Skip to content

Commit da57d50

Browse files
authored
[Containerapp] Adding Foundry deployment support in containerapp up (#8744)
1 parent 9d17c88 commit da57d50

File tree

9 files changed

+10547
-16
lines changed

9 files changed

+10547
-16
lines changed

src/containerapp/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ upcoming
77
* 'az containerapp auth update': Support authenticating blob storage token store using managed identity with `--blob-container-uri` and `--blob-container-identity`.
88
* 'az containerapp env create': Set identity only when `--mi-system-assigned` or `--mi-user-assigned` is specified.
99
* 'az containerapp env create': Set identity only when `--system-assigned` or `--user-assigned` is specified.
10+
* 'az containerapp up': Support deploying Azure AI Foundry model to Container App with `--model-registry`, `--model-name`, `--model-version`.
1011

1112
1.1.0b4
1213
++++++

src/containerapp/azext_containerapp/_help.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@
158158
- name: Create a container app from an image in a registry on a connected environment
159159
text: |
160160
az containerapp up -n my-containerapp --image myregistry.azurecr.io/myImage:myTag --environment MyConnectedEnvironmentId
161+
- name: Create a container app and deploy a model from Azure AI Foundry
162+
text: |
163+
az containerapp up -n my-containerapp -l westus3 --model-registry azureml --model-name Phi-4 --model-version 7
161164
"""
162165

163166

src/containerapp/azext_containerapp/_params.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ def load_arguments(self, _):
276276
c.argument('user_assigned', nargs='+', help="Space-separated user identities to be assigned.")
277277
c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.", action='store_true')
278278

279+
with self.argument_context('containerapp up', arg_group='Deploy an Azure AI Foundry Model', is_preview=True) as c:
280+
c.argument('model_registry', help="The name of the Azure AI Foundry model registry.", is_preview=True)
281+
c.argument('model_name', help="The name of the Azure AI Foundry model.", is_preview=True)
282+
c.argument('model_version', help="The version of the Azure AI Foundry model.", is_preview=True)
283+
279284
with self.argument_context('containerapp auth') as c:
280285
c.argument('blob_container_uri', help='The URI of the blob storage containing the tokens. Should not be used along with sas_url_secret and sas_url_secret_name.', is_preview=True)
281286
c.argument('blob_container_identity', options_list=['--blob-container-identity', '--bci'],

src/containerapp/azext_containerapp/_up_utils.py

Lines changed: 226 additions & 5 deletions
Large diffs are not rendered by default.

src/containerapp/azext_containerapp/containerapp_env_decorator.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,20 @@ def set_up_workload_profiles(self):
133133
"maximumCount": 1
134134
}
135135
workload_profiles.append(gpu_profile)
136+
if self.is_env_for_azml_app() and not self.get_argument_enable_dedicated_gpu():
137+
wp_type = self.get_argument_workload_profile_type()
138+
if wp_type is None or wp_type.lower() == "consumption-gpu-nc24-a100":
139+
serverless_a100_profile = {
140+
"workloadProfileType": "Consumption-GPU-NC24-A100",
141+
"name": self.get_argument_workload_profile_name() if self.get_argument_workload_profile_name() else "serverless-A100",
142+
}
143+
workload_profiles.append(serverless_a100_profile)
144+
else:
145+
serverless_gpu_profile = {
146+
"workloadProfileType": wp_type,
147+
"name": self.get_argument_workload_profile_name() if self.get_argument_workload_profile_name() else "serverless-gpu",
148+
}
149+
workload_profiles.append(serverless_gpu_profile)
136150
self.managed_env_def["properties"]["workloadProfiles"] = workload_profiles
137151

138152
def set_up_custom_domain_configuration(self):
@@ -182,6 +196,15 @@ def get_argument_certificate_key_vault_url(self):
182196
def get_argument_public_network_access(self):
183197
return self.get_param("public_network_access")
184198

199+
def is_env_for_azml_app(self):
200+
return self.get_param("is_env_for_azml_app")
201+
202+
def get_argument_workload_profile_type(self):
203+
return self.get_param("workload_profile_type")
204+
205+
def get_argument_workload_profile_name(self):
206+
return self.get_param("workload_profile_name")
207+
185208

186209
class ContainerappEnvPreviewUpdateDecorator(ContainerAppEnvUpdateDecorator):
187210
def validate_arguments(self):

src/containerapp/azext_containerapp/custom.py

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,79 @@ def create_managed_environment(cmd,
750750
system_assigned=False,
751751
user_assigned=None,
752752
public_network_access=None):
753+
return create_managed_environment_logic(
754+
cmd=cmd,
755+
name=name,
756+
resource_group_name=resource_group_name,
757+
logs_destination=logs_destination,
758+
storage_account=storage_account,
759+
logs_customer_id=logs_customer_id,
760+
logs_key=logs_key,
761+
location=location,
762+
instrumentation_key=instrumentation_key,
763+
dapr_connection_string=dapr_connection_string,
764+
infrastructure_subnet_resource_id=infrastructure_subnet_resource_id,
765+
infrastructure_resource_group=infrastructure_resource_group,
766+
docker_bridge_cidr=docker_bridge_cidr,
767+
platform_reserved_cidr=platform_reserved_cidr,
768+
platform_reserved_dns_ip=platform_reserved_dns_ip,
769+
internal_only=internal_only,
770+
tags=tags,
771+
disable_warnings=disable_warnings,
772+
zone_redundant=zone_redundant,
773+
hostname=hostname,
774+
certificate_file=certificate_file,
775+
certificate_password=certificate_password,
776+
certificate_identity=certificate_identity,
777+
certificate_key_vault_url=certificate_key_vault_url,
778+
enable_workload_profiles=enable_workload_profiles,
779+
mtls_enabled=mtls_enabled,
780+
p2p_encryption_enabled=p2p_encryption_enabled,
781+
enable_dedicated_gpu=enable_dedicated_gpu,
782+
no_wait=no_wait,
783+
logs_dynamic_json_columns=logs_dynamic_json_columns,
784+
system_assigned=system_assigned,
785+
user_assigned=user_assigned,
786+
public_network_access=public_network_access
787+
)
788+
789+
790+
def create_managed_environment_logic(cmd,
791+
name,
792+
resource_group_name,
793+
logs_destination="log-analytics",
794+
storage_account=None,
795+
logs_customer_id=None,
796+
logs_key=None,
797+
location=None,
798+
instrumentation_key=None,
799+
dapr_connection_string=None,
800+
infrastructure_subnet_resource_id=None,
801+
infrastructure_resource_group=None,
802+
docker_bridge_cidr=None,
803+
platform_reserved_cidr=None,
804+
platform_reserved_dns_ip=None,
805+
internal_only=False,
806+
tags=None,
807+
disable_warnings=False,
808+
zone_redundant=False,
809+
hostname=None,
810+
certificate_file=None,
811+
certificate_password=None,
812+
certificate_identity=None,
813+
certificate_key_vault_url=None,
814+
enable_workload_profiles=True,
815+
mtls_enabled=None,
816+
p2p_encryption_enabled=None,
817+
enable_dedicated_gpu=False,
818+
no_wait=False,
819+
logs_dynamic_json_columns=False,
820+
system_assigned=False,
821+
user_assigned=None,
822+
public_network_access=None,
823+
workload_profile_type=None,
824+
workload_profile_name=None,
825+
is_env_for_azml_app=False):
753826
raw_parameters = locals()
754827
containerapp_env_create_decorator = ContainerappEnvPreviewCreateDecorator(
755828
cmd=cmd,
@@ -1244,16 +1317,27 @@ def containerapp_up(cmd,
12441317
user_assigned=None,
12451318
system_assigned=None,
12461319
custom_location_id=None,
1247-
connected_cluster_id=None):
1320+
connected_cluster_id=None,
1321+
model_registry=None,
1322+
model_name=None,
1323+
model_version=None):
12481324
from ._up_utils import (_validate_up_args, _validate_custom_location_connected_cluster_args, _reformat_image, _get_dockerfile_content, _get_ingress_and_target_port,
12491325
ResourceGroup, Extension, CustomLocation, ContainerAppEnvironment, ContainerApp, _get_registry_from_app,
12501326
_get_registry_details, _get_registry_details_without_get_creds, _create_github_action, _set_up_defaults, up_output,
1251-
check_env_name_on_rg, get_token, _has_dockerfile)
1327+
check_env_name_on_rg, get_token, _has_dockerfile, _validate_azml_args, _is_azml_app, _validate_azml_env_and_create_if_needed, _set_azml_env_vars)
12521328
from azure.cli.command_modules.containerapp._github_oauth import cache_github_token
12531329
HELLOWORLD = "mcr.microsoft.com/k8se/quickstart"
1330+
ACA_AZML_MCR = "mcr.microsoft.com/k8se/aca-foundry-model-host"
12541331
dockerfile = "Dockerfile" # for now the dockerfile name must be "Dockerfile" (until GH actions API is updated)
12551332

12561333
register_provider_if_needed(cmd, CONTAINER_APPS_RP)
1334+
is_azureml_app = _is_azml_app(model_registry, model_name, model_version)
1335+
model_asset_id = None
1336+
model_reference_endpoint = None
1337+
if is_azureml_app:
1338+
model_asset_id, model_reference_endpoint, model_type, model_load_class, image = _validate_azml_args(cmd, ACA_AZML_MCR, image, model_registry, model_name, model_version)
1339+
env_vars = _set_azml_env_vars(cmd, env_vars, model_asset_id, model_reference_endpoint, model_type, model_load_class, is_azml_mcr_app=image.lower() == ACA_AZML_MCR.lower())
1340+
12571341
_validate_up_args(cmd, source, artifact, build_env_vars, image, repo, registry_server)
12581342
validate_create(registry_identity=registry_identity,
12591343
registry_pass=registry_pass,
@@ -1280,6 +1364,10 @@ def containerapp_up(cmd,
12801364
ingress = "external" if not ingress else ingress
12811365
target_port = 80 if not target_port else target_port
12821366

1367+
if image and ACA_AZML_MCR in image.lower():
1368+
ingress = "external" if not ingress else ingress
1369+
target_port = 8000 if not target_port else target_port
1370+
12831371
if image:
12841372
if ingress and not target_port and target_port != 0:
12851373
target_port = 0
@@ -1294,7 +1382,7 @@ def containerapp_up(cmd,
12941382
resource_group = ResourceGroup(cmd, name=resource_group_name, location=location)
12951383
custom_location = CustomLocation(cmd, name=custom_location_id, resource_group_name=resource_group_name, connected_cluster_id=connected_cluster_id)
12961384
extension = Extension(cmd, logs_rg=resource_group_name, logs_location=location, logs_share_key=logs_key, logs_customer_id=logs_customer_id, connected_cluster_id=connected_cluster_id)
1297-
env = ContainerAppEnvironment(cmd, environment, resource_group, location=location, logs_key=logs_key, logs_customer_id=logs_customer_id, custom_location_id=custom_location_id, connected_cluster_id=connected_cluster_id)
1385+
env = ContainerAppEnvironment(cmd, environment, resource_group, location=location, logs_key=logs_key, logs_customer_id=logs_customer_id, custom_location_id=custom_location_id, connected_cluster_id=connected_cluster_id, is_env_for_azml_app=is_azureml_app)
12981386
app = ContainerApp(cmd, name, resource_group, None, image, env, target_port, registry_server, registry_user, registry_pass, env_vars, workload_profile_name, ingress, registry_identity=registry_identity, user_assigned=user_assigned, system_assigned=system_assigned, revisions_mode=revisions_mode, target_label=target_label)
12991387

13001388
# Check and see if registry (username and passwords) or registry-identity are specified. If so, set is_registry_server_params_set to True to use those creds.
@@ -1305,6 +1393,12 @@ def containerapp_up(cmd,
13051393
if app.get()["properties"]["provisioningState"] == "InProgress":
13061394
raise ValidationError("Containerapp has an existing provisioning in progress. Please wait until provisioning has completed and rerun the command.")
13071395

1396+
if is_azureml_app:
1397+
env, workload_profile_name, app_cpu_limit, app_memory_limit = _validate_azml_env_and_create_if_needed(cmd, app, env, environment, resource_group, resource_group_name, workload_profile_name)
1398+
app.workload_profile_name = workload_profile_name
1399+
app.cpu = app_cpu_limit
1400+
app.memory = app_memory_limit
1401+
13081402
resource_group.create_if_needed()
13091403
extension.create_if_needed()
13101404
custom_location.create_if_needed()
@@ -1337,7 +1431,7 @@ def containerapp_up(cmd,
13371431
up_output(app, no_dockerfile=(source and not _has_dockerfile(source, dockerfile)))
13381432

13391433

1340-
def containerapp_up_logic(cmd, resource_group_name, name, managed_env, image, env_vars, ingress, target_port, registry_server, registry_user, workload_profile_name, registry_pass, environment_type=None, force_single_container_updates=False, registry_identity=None, system_assigned=None, user_assigned=None, revisions_mode=None, target_label=None):
1434+
def containerapp_up_logic(cmd, resource_group_name, name, managed_env, image, env_vars, ingress, target_port, registry_server, registry_user, workload_profile_name, registry_pass, environment_type=None, force_single_container_updates=False, registry_identity=None, system_assigned=None, user_assigned=None, revisions_mode=None, target_label=None, cpu=None, memory=None):
13411435
containerapp_def = None
13421436
try:
13431437
containerapp_def = ContainerAppPreviewClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
@@ -1349,7 +1443,7 @@ def containerapp_up_logic(cmd, resource_group_name, name, managed_env, image, en
13491443
registry_server=registry_server, registry_user=registry_user, registry_pass=registry_pass, workload_profile_name=workload_profile_name, container_name=name, force_single_container_updates=force_single_container_updates,
13501444
registry_identity=registry_identity, system_assigned=system_assigned, user_assigned=user_assigned, revisions_mode=revisions_mode, target_label=target_label)
13511445
return create_containerapp(cmd=cmd, name=name, resource_group_name=resource_group_name, managed_env=managed_env, image=image, env_vars=env_vars, ingress=ingress, target_port=target_port, registry_server=registry_server, registry_user=registry_user, registry_pass=registry_pass, workload_profile_name=workload_profile_name, environment_type=environment_type,
1352-
registry_identity=registry_identity, system_assigned=system_assigned, user_assigned=user_assigned, revisions_mode=revisions_mode, target_label=target_label)
1446+
registry_identity=registry_identity, system_assigned=system_assigned, user_assigned=user_assigned, revisions_mode=revisions_mode, target_label=target_label, cpu=cpu, memory=memory)
13531447

13541448

13551449
def list_certificates(cmd, name, resource_group_name, location=None, certificate=None, thumbprint=None, managed_certificates_only=False, private_key_certificates_only=False):

0 commit comments

Comments
 (0)