def apply(self, deployment_pb, repo, prev_deployment=None): deployment_spec = deployment_pb.spec gcp_config = deployment_spec.gcp_function_operator_config bento_path = repo.get(deployment_spec.bento_name, deployment_spec.bento_version) bento_config = load_bentoml_config(bento_path) with TemporaryServerlessContent( archive_path=bento_path, deployment_name=deployment_pb.name, bento_name=deployment_spec.bento_name, template_type='google-python', ) as output_path: generate_main_py(bento_config['name'], bento_config['apis'], output_path) generate_serverless_configuration_for_gcp_function( service_name=bento_config['name'], apis=bento_config['apis'], output_path=output_path, region=gcp_config.region, stage=deployment_pb.namespace, ) call_serverless_command(["deploy"], output_path) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) state = self.describe(res_deployment_pb, repo).state res_deployment_pb.state.CopyFrom(state) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)
def _apply(self, deployment_pb, bento_pb, yatai_service, bento_path): if loader._is_remote_path(bento_path): with loader._resolve_remote_bundle_path(bento_path) as local_path: return self._apply(deployment_pb, bento_pb, yatai_service, local_path) deployment_spec = deployment_pb.spec aws_config = deployment_spec.aws_lambda_operator_config bento_service_metadata = bento_pb.bento.bento_service_metadata template = 'aws-python3' if version.parse(bento_service_metadata.env.python_version ) < version.parse('3.0.0'): template = 'aws-python' api_names = ([aws_config.api_name] if aws_config.api_name else [api.name for api in bento_service_metadata.apis]) ensure_deploy_api_name_exists_in_bento( [api.name for api in bento_service_metadata.apis], api_names) with TempDirectory() as serverless_project_dir: init_serverless_project_dir( serverless_project_dir, bento_path, deployment_pb.name, deployment_spec.bento_name, template, ) generate_aws_lambda_handler_py(deployment_spec.bento_name, api_names, serverless_project_dir) generate_aws_lambda_serverless_config( bento_service_metadata.env.python_version, deployment_pb.name, api_names, serverless_project_dir, aws_config.region, # BentoML deployment namespace is mapping to serverless `stage` # concept stage=deployment_pb.namespace, ) logger.info( 'Installing additional packages: serverless-python-requirements' ) install_serverless_plugin("serverless-python-requirements", serverless_project_dir) logger.info('Deploying to AWS Lambda') call_serverless_command(["deploy"], serverless_project_dir) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) state = self.describe(res_deployment_pb, yatai_service).state res_deployment_pb.state.CopyFrom(state) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)
def apply(self, deployment_pb, repo): # deploy code..... spec = deployment_pb.spec bento_path = repo.get(spec.bento_name, spec.bento_version) # config = load_bentoml_config(bento_path)... res_deployment_pb = Deployment() res_deployment_pb.CopyFrom(deployment_pb) # res_deployment_pb.state = ... return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)
def apply(self, deployment_pb, yatai_service, prev_deployment=None): try: deployment_spec = deployment_pb.spec gcp_config = deployment_spec.gcp_function_operator_config bento_pb = yatai_service.GetBento( GetBentoRequest( bento_name=deployment_spec.bento_name, bento_version=deployment_spec.bento_version, )) if bento_pb.bento.uri.type != BentoUri.LOCAL: raise BentoMLException( 'BentoML currently only support local repository') else: bento_path = bento_pb.bento.uri.uri bento_service_metadata = bento_pb.bento.bento_service_metadata api_names = ([gcp_config.api_name] if gcp_config.api_name else [api.name for api in bento_service_metadata.apis]) ensure_deploy_api_name_exists_in_bento( [api.name for api in bento_service_metadata.apis], api_names) with TempDirectory() as serverless_project_dir: init_serverless_project_dir( serverless_project_dir, bento_path, deployment_pb.name, deployment_spec.bento_name, 'google-python', ) generate_gcp_function_main_py(deployment_spec.bento_name, api_names, serverless_project_dir) generate_gcp_function_serverless_config( deployment_pb.name, api_names, serverless_project_dir, gcp_config.region, # BentoML namespace is mapping to serverless stage. stage=deployment_pb.namespace, ) call_serverless_command(["deploy"], serverless_project_dir) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) state = self.describe(res_deployment_pb, yatai_service).state res_deployment_pb.state.CopyFrom(state) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb) except BentoMLException as error: return ApplyDeploymentResponse( status=exception_to_return_status(error))
def create_sagemaker_deployment( self, name, bento_name, bento_version, api_name, instance_type, instance_count, timeout, num_of_gunicorn_workers_per_instance=None, region=None, namespace=None, labels=None, annotations=None, wait=None, ): """Create SageMaker deployment Args: name: bento_name: bento_version: api_name: instance_type: instance_count: timeout: num_of_gunicorn_workers_per_instance: region: namespace: labels: annotations: wait: Returns: ApplyDeploymentResponse Raises: BentoMLException """ namespace = (namespace if namespace else config().get( 'deployment', 'default_namespace')) deployment_pb = Deployment(name=name, namespace=namespace, labels=labels, annotations=annotations) deployment_pb.spec.bento_name = bento_name deployment_pb.spec.bento_version = bento_version deployment_pb.spec.operator = DeploymentSpec.AWS_SAGEMAKER deployment_pb.spec.sagemaker_operator_config.api_name = api_name deployment_pb.spec.sagemaker_operator_config.instance_count = instance_count deployment_pb.spec.sagemaker_operator_config.instance_type = instance_type deployment_pb.spec.sagemaker_operator_config.timeout = timeout if region: deployment_pb.spec.sagemaker_operator_config.region = region if num_of_gunicorn_workers_per_instance: deployment_pb.spec.sagemaker_operator_config.num_of_gunicorn_workers_per_instance = ( # noqa E501 num_of_gunicorn_workers_per_instance) return self.create(deployment_pb, wait)
def test_validate_aws_lambda_schema(): test_pb = Deployment(name='test_deployment_name', namespace='namespace') test_pb.spec.bento_name = 'bento_name' test_pb.spec.bento_version = 'bento_version' test_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( 'AWS_LAMBDA') test_pb.spec.aws_lambda_operator_config.api_name = 'api_name' test_pb.spec.aws_lambda_operator_config.region = 'us-west-2' test_pb.spec.aws_lambda_operator_config.timeout = 100 test_pb.spec.aws_lambda_operator_config.memory_size = 128 result = validate_deployment_pb_schema(test_pb) assert result is None test_pb.spec.aws_lambda_operator_config.timeout = 1000 test_pb.spec.aws_lambda_operator_config.memory_size = 129 failed_memory_test = validate_deployment_pb_schema(test_pb) print(failed_memory_test) aws_spec_fail_msg = failed_memory_test['spec'][0][ 'aws_lambda_operator_config'][0] assert aws_spec_fail_msg['memory_size'] assert 'AWS Lambda memory' in aws_spec_fail_msg['memory_size'][0] assert aws_spec_fail_msg['timeout'] assert 'max value is 900' in aws_spec_fail_msg['timeout'][0]
def apply(self, deployment_pb, repo, prev_deployment=None): ensure_docker_available_or_raise() deployment_spec = deployment_pb.spec aws_config = deployment_spec.aws_lambda_operator_config bento_path = repo.get(deployment_spec.bento_name, deployment_spec.bento_version) bento_config = load_bentoml_config(bento_path) template = 'aws-python3' minimum_python_version = version.parse('3.0.0') bento_python_version = version.parse( bento_config['env']['python_version']) if bento_python_version < minimum_python_version: template = 'aws-python' with TemporaryServerlessContent( archive_path=bento_path, deployment_name=deployment_pb.name, bento_name=deployment_spec.bento_name, template_type=template, ) as output_path: generate_handler_py(deployment_spec.bento_name, bento_config['apis'], output_path) generate_serverless_configuration_for_aws_lambda( service_name=deployment_pb.name, apis=bento_config['apis'], output_path=output_path, region=aws_config.region, stage=deployment_pb.namespace, ) logger.info( 'Installing additional packages: serverless-python-requirements, ' 'serverless-apigw-binary') install_serverless_plugin("serverless-python-requirements", output_path) install_serverless_plugin("serverless-apigw-binary", output_path) logger.info('Deploying to AWS Lambda') call_serverless_command(["deploy"], output_path) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) state = self.describe(res_deployment_pb, repo).state res_deployment_pb.state.CopyFrom(state) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)
def _get_test_sagemaker_deployment_pb(): test_pb = Deployment(name='test_deployment_name', namespace='namespace') test_pb.spec.bento_name = 'bento_name' test_pb.spec.bento_version = 'bento_version' test_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( 'AWS_SAGEMAKER') test_pb.spec.sagemaker_operator_config.api_name = 'api_name' test_pb.spec.sagemaker_operator_config.instance_type = 'mock_instance_type' test_pb.spec.sagemaker_operator_config.instance_count = 1 return test_pb
def generate_lambda_deployment_pb(): test_deployment_pb = Deployment(name='test_aws_lambda', namespace='test-namespace') test_deployment_pb.spec.bento_name = 'bento_name' test_deployment_pb.spec.bento_version = 'v1.0.0' # DeploymentSpec.DeploymentOperator.AWS_LAMBDA test_deployment_pb.spec.operator = 3 test_deployment_pb.spec.aws_lambda_operator_config.region = 'us-west-2' test_deployment_pb.spec.aws_lambda_operator_config.api_name = 'predict' return test_deployment_pb
def _get_test_lambda_deployment_pb(): test_pb = Deployment(name='test_deployment_name', namespace='namespace') test_pb.spec.bento_name = 'bento_name' test_pb.spec.bento_version = 'bento_version' test_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( 'AWS_LAMBDA') test_pb.spec.aws_lambda_operator_config.api_name = 'api_name' test_pb.spec.aws_lambda_operator_config.region = 'us-west-2' test_pb.spec.aws_lambda_operator_config.timeout = 100 test_pb.spec.aws_lambda_operator_config.memory_size = 128 return test_pb
def create_lambda_deployment( self, name, bento_name, bento_version, memory_size, timeout, api_name=None, region=None, namespace=None, labels=None, annotations=None, wait=None, ): """Create Lambda deployment Args: name: bento_name: bento_version: memory_size: timeout: api_name: region: namespace: labels: annotations: wait: Returns: ApplyDeploymentResponse: status, deployment Raises: BentoMLException """ namespace = (namespace if namespace else config().get( 'deployment', 'default_namespace')) deployment_pb = Deployment(name=name, namespace=namespace, labels=labels, annotations=annotations) deployment_pb.spec.bento_name = bento_name deployment_pb.spec.bento_version = bento_version deployment_pb.spec.operator = DeploymentSpec.AWS_LAMBDA deployment_pb.spec.aws_lambda_operator_config.memory_size = memory_size deployment_pb.spec.aws_lambda_operator_config.timeout = timeout if api_name: deployment_pb.spec.aws_lambda_operator_config.api_name = api_name if region: deployment_pb.spec.aws_lambda_operator_config.region = region return self.create(deployment_pb, wait)
def generate_sagemaker_deployment_pb(): test_deployment_pb = Deployment(name=TEST_DEPLOYMENT_NAME, namespace=TEST_DEPLOYMENT_NAMESPACE) test_deployment_pb.spec.bento_name = TEST_DEPLOYMENT_BENTO_NAME test_deployment_pb.spec.bento_version = TEST_DEPLOYMENT_BENTO_VERSION test_deployment_pb.spec.sagemaker_operator_config.api_name = TEST_BENTO_API_NAME test_deployment_pb.spec.sagemaker_operator_config.region = TEST_AWS_REGION test_deployment_pb.spec.sagemaker_operator_config.instance_count = ( TEST_DEPLOYMENT_INSTANCE_COUNT) test_deployment_pb.spec.sagemaker_operator_config.instance_type = ( TEST_DEPLOYMENT_INSTANCE_TYPE) test_deployment_pb.spec.operator = DeploymentSpec.AWS_SAGEMAKER return test_deployment_pb
def test_validate_deployment_pb_schema(): test_pb = Deployment(name='test_deployment_name', namespace='namespace') test_pb.spec.bento_name = 'bento_name' test_pb.spec.bento_version = 'bento_version' test_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( 'AWS_SAGEMAKER') test_pb.spec.sagemaker_operator_config.api_name = 'api_name' result = validate_deployment_pb_schema(test_pb) assert result is None test_bad_pb = test_pb test_bad_pb.name = '' bad_result = validate_deployment_pb_schema(test_bad_pb) assert bad_result == {'name': ['required field']}
def apply(self, deployment_pb, yatai_service, prev_deployment=None): try: ensure_docker_available_or_raise() deployment_spec = deployment_pb.spec aws_config = deployment_spec.aws_lambda_operator_config bento_pb = yatai_service.GetBento( GetBentoRequest( bento_name=deployment_spec.bento_name, bento_version=deployment_spec.bento_version, ) ) if bento_pb.bento.uri.type != BentoUri.LOCAL: raise BentoMLException( 'BentoML currently only support local repository' ) else: bento_path = bento_pb.bento.uri.uri bento_service_metadata = bento_pb.bento.bento_service_metadata template = 'aws-python3' if version.parse(bento_service_metadata.env.python_version) < version.parse( '3.0.0' ): template = 'aws-python' api_names = ( [aws_config.api_name] if aws_config.api_name else [api.name for api in bento_service_metadata.apis] ) ensure_deploy_api_name_exists_in_bento( [api.name for api in bento_service_metadata.apis], api_names ) with TempDirectory() as serverless_project_dir: init_serverless_project_dir( serverless_project_dir, bento_path, deployment_pb.name, deployment_spec.bento_name, template, ) generate_aws_lambda_handler_py( deployment_spec.bento_name, api_names, serverless_project_dir ) generate_aws_lambda_serverless_config( bento_service_metadata.env.python_version, deployment_pb.name, api_names, serverless_project_dir, aws_config.region, # BentoML deployment namespace is mapping to serverless `stage` # concept stage=deployment_pb.namespace, ) logger.info( 'Installing additional packages: serverless-python-requirements' ) install_serverless_plugin( "serverless-python-requirements", serverless_project_dir ) logger.info('Deploying to AWS Lambda') call_serverless_command(["deploy"], serverless_project_dir) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) state = self.describe(res_deployment_pb, yatai_service).state res_deployment_pb.state.CopyFrom(state) return ApplyDeploymentResponse( status=Status.OK(), deployment=res_deployment_pb ) except BentoMLException as error: return ApplyDeploymentResponse(status=exception_to_return_status(error))
def apply( bento, deployment_name, platform, output, namespace, labels, annotations, region, stage, instance_type, instance_count, api_name, kube_namespace, replicas, service_name, service_type, ): track_cli('deploy-apply', platform) bento_name, bento_verison = bento.split(':') spec = DeploymentSpec( bento_name=bento_name, bento_verison=bento_verison, operator=get_deployment_operator_type(platform), ) if platform == 'aws_sagemaker': spec.sagemaker_operator_config = DeploymentSpec.SageMakerOperatorConfig( region=region, instance_count=instance_count, instance_type=instance_type, api_name=api_name, ) elif platform == 'aws_lambda': spec.aws_lambda_operator_config = DeploymentSpec.AwsLambdaOperatorConfig( region=region, stage=stage) elif platform == 'gcp_function': spec.gcp_function_operator_config = \ DeploymentSpec.GcpFunctionOperatorConfig( region=region, stage=stage ) elif platform == 'kubernetes': spec.kubernetes_operator_config = DeploymentSpec.KubernetesOperatorConfig( kube_namespace=kube_namespace, replicas=replicas, service_name=service_name, service_type=service_type, ) else: raise BentoMLDeploymentException( 'Custom deployment is not supported in current version of BentoML' ) result = get_yatai_service().ApplyDeployment( ApplyDeploymentRequest(deployment=Deployment( namespace=namespace, name=deployment_name, annotations=parse_key_value_pairs(annotations), labels=parse_key_value_pairs(labels), spec=spec, ))) if result.status.status_code != Status.OK: _echo( 'Failed to apply deployment {name}. code: {error_code}, message: ' '{error_message}'.format( name=deployment_name, error_code=Status.Code.Name(result.status.status_code), error_message=result.status.error_message, ), CLI_COLOR_ERROR, ) else: _echo( 'Successfully apply deployment {}'.format(deployment_name), CLI_COLOR_SUCCESS, ) display_deployment_info(result.deployment, output)
def deployment_dict_to_pb(deployment_dict): deployment_pb = Deployment() if deployment_dict.get('name') is not None: deployment_pb.name = deployment_dict.get('name') if deployment_dict.get('namespace') is not None: deployment_pb.namespace = deployment_dict.get('namespace') if deployment_dict.get('labels') is not None: deployment_pb.labels.update(deployment_dict.get('labels')) if deployment_dict.get('annotations') is not None: deployment_pb.annotations.update(deployment_dict.get('annotations')) if deployment_dict.get('spec'): spec_dict = deployment_dict.get('spec') else: raise BentoMLDeploymentException( '"spec" is required field for deployment') platform = spec_dict.get('operator') if platform is not None: # converting platform parameter to DeploymentOperator name in proto # e.g. 'aws-lambda' to 'AWS_LAMBDA' deployment_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( platform.replace('-', '_').upper()) if spec_dict.get('bento_name'): deployment_pb.spec.bento_name = spec_dict.get('bento_name') if spec_dict.get('bento_version'): deployment_pb.spec.bento_version = spec_dict.get('bento_version') if deployment_pb.spec.operator == DeploymentSpec.AWS_SAGEMAKER: sagemaker_config = spec_dict.get('sagemaker_operator_config', {}) sagemaker_operator_config_pb = deployment_pb.spec.sagemaker_operator_config if sagemaker_config.get('api_name'): sagemaker_operator_config_pb.api_name = sagemaker_config.get( 'api_name') if sagemaker_config.get('region'): sagemaker_operator_config_pb.region = sagemaker_config.get( 'region') if sagemaker_config.get('instance_count'): sagemaker_operator_config_pb.instance_count = int( sagemaker_config.get('instance_count')) if sagemaker_config.get('instance_type'): sagemaker_operator_config_pb.instance_type = sagemaker_config.get( 'instance_type') elif deployment_pb.spec.operator == DeploymentSpec.AWS_LAMBDA: lambda_config = spec_dict.get('aws_lambda_operator_config', {}) if lambda_config.get('region'): deployment_pb.spec.aws_lambda_operator_config.region = lambda_config.get( 'region') if lambda_config.get('api_name'): deployment_pb.spec.aws_lambda_operator_config.api_name = lambda_config.get( 'api_name') elif deployment_pb.spec.operator == DeploymentSpec.GCP_FUNCTION: gcp_config = spec_dict.get('gcp_function_operator_config', {}) if gcp_config.get('region'): deployment_pb.spec.gcp_function_operator_config.region = gcp_config.get( 'region') if gcp_config.get('api_name'): deployment_pb.spec.aws_lambda_operator_config.api_name = gcp_config.get( 'api_name') elif deployment_pb.spec.operator == DeploymentSpec.KUBERNETES: k8s_config = spec_dict.get('kubernetes_operator_config', {}) k8s_operator_config_pb = deployment_pb.spec.kubernetes_operator_config if k8s_config.get('kube_namespace'): k8s_operator_config_pb.kube_namespace = k8s_config.get( 'kube_namespace') if k8s_config.get('replicas'): k8s_operator_config_pb.replicas = k8s_config.get('replicas') if k8s_config.get('service_name'): k8s_operator_config_pb.service_name = k8s_config.get( 'service_name') if k8s_config.get('service_type'): k8s_operator_config_pb.service_type = k8s_config.get( 'service_type') else: raise BentoMLException( 'Platform "{}" is not supported in the current version of ' 'BentoML'.format(platform)) return deployment_pb
def create( name, bento, platform, output, namespace, labels, annotations, region, instance_type, instance_count, api_name, kube_namespace, replicas, service_name, service_type, wait, ): # converting platform parameter to DeploymentOperator name in proto # e.g. 'aws-lambda' to 'AWS_LAMBDA' platform = platform.replace('-', '_').upper() operator = DeploymentSpec.DeploymentOperator.Value(platform) track_cli('deploy-create', platform) yatai_service = get_yatai_service() # Make sure there is no active deployment with the same deployment name get_deployment = yatai_service.GetDeployment( GetDeploymentRequest(deployment_name=name, namespace=namespace)) if get_deployment.status.status_code != Status.NOT_FOUND: raise BentoMLDeploymentException( 'Deployment {name} already existed, please use update or apply command' ' instead'.format(name=name)) if operator == DeploymentSpec.AWS_SAGEMAKER: if not api_name: raise click.BadParameter( 'api-name is required for Sagemaker deployment') sagemaker_operator_config = DeploymentSpec.SageMakerOperatorConfig( region=region or config().get('aws', 'default_region'), instance_count=instance_count or config().getint('sagemaker', 'instance_count'), instance_type=instance_type or config().get('sagemaker', 'instance_type'), api_name=api_name, ) spec = DeploymentSpec( sagemaker_operator_config=sagemaker_operator_config) elif operator == DeploymentSpec.AWS_LAMBDA: aws_lambda_operator_config = DeploymentSpec.AwsLambdaOperatorConfig( region=region or config().get('aws', 'default_region')) if api_name: aws_lambda_operator_config.api_name = api_name spec = DeploymentSpec( aws_lambda_operator_config=aws_lambda_operator_config) elif operator == DeploymentSpec.GCP_FUNCTION: gcp_function_operator_config = DeploymentSpec.GcpFunctionOperatorConfig( region=region or config().get('google-cloud', 'default_region')) if api_name: gcp_function_operator_config.api_name = api_name spec = DeploymentSpec( gcp_function_operator_config=gcp_function_operator_config) elif operator == DeploymentSpec.KUBERNETES: kubernetes_operator_config = DeploymentSpec.KubernetesOperatorConfig( kube_namespace=kube_namespace, replicas=replicas, service_name=service_name, service_type=service_type, ) spec = DeploymentSpec( kubernetes_operator_config=kubernetes_operator_config) else: raise BentoMLDeploymentException( 'Custom deployment is not supported in the current version of BentoML' ) bento_name, bento_version = bento.split(':') spec.bento_name = bento_name spec.bento_version = bento_version spec.operator = operator result = yatai_service.ApplyDeployment( ApplyDeploymentRequest(deployment=Deployment( namespace=namespace, name=name, annotations=parse_key_value_pairs(annotations), labels=parse_key_value_pairs(labels), spec=spec, ))) if result.status.status_code != Status.OK: _echo( 'Failed to create deployment {name}. {error_code}: ' '{error_message}'.format( name=name, error_code=Status.Code.Name(result.status.status_code), error_message=result.status.error_message, ), CLI_COLOR_ERROR, ) else: if wait: result_state = get_state_after_await_action_complete( yatai_service=yatai_service, name=name, namespace=namespace, message='Creating deployment ', ) result.deployment.state.CopyFrom(result_state.state) _echo('Successfully created deployment {}'.format(name), CLI_COLOR_SUCCESS) display_deployment_info(result.deployment, output)
def deployment_yaml_to_pb(deployment_yaml): deployment_pb = Deployment() if deployment_yaml.get('name') is not None: deployment_pb.name = deployment_yaml.get('name') if deployment_yaml.get('namespace') is not None: deployment_pb.namespace = deployment_yaml.get('namespace') if deployment_yaml.get('labels') is not None: deployment_pb.labels.update(dict(deployment_yaml.get('labels'))) if deployment_yaml.get('annotations') is not None: deployment_pb.annotations.update( dict(deployment_yaml.get('annotations'))) spec_yaml = deployment_yaml.get('spec') platform = spec_yaml.get('operator') if platform is not None: deployment_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( platform.replace('-', '_').upper()) if spec_yaml.get('bento_name'): deployment_pb.spec.bento_name = spec_yaml.get('bento_name') if spec_yaml.get('bento_version'): deployment_pb.spec.bento_version = spec_yaml.get('bento_version') if platform == 'aws_sagemaker': sagemaker_config = spec_yaml.get('sagemaker_operator_config') sagemaker_operator_config_pb = deployment_pb.spec.sagemaker_operator_config if sagemaker_config.get('api_name'): sagemaker_operator_config_pb.api_name = sagemaker_config.get( 'api_name') if sagemaker_config.get('region'): sagemaker_operator_config_pb.region = sagemaker_config.get( 'region') if sagemaker_config.get('instance_count'): sagemaker_operator_config_pb.instance_count = sagemaker_config.get( 'instance_count') if sagemaker_config.get('instance_type'): sagemaker_operator_config_pb.instance_type = sagemaker_config.get( 'instance_type') elif platform == 'aws_lambda': lambda_config = spec_yaml.get('aws_lambda_operator_config') if lambda_config.get('region'): deployment_pb.spec.aws_lambda_config.region = lambda_config.get( 'region') elif platform == 'gcp_function': gcp_config = spec_yaml.get('gcp_function_operator_config') if gcp_config.get('region'): deployment_pb.spec.gcp_function_operator_config.region = gcp_config.get( 'region') elif platform == 'kubernetes': k8s_config = spec_yaml.get('kubernetes_operator_config') k8s_operator_config_pb = deployment_pb.spec.kubernetes_operator_config if k8s_config.get('kube_namespace'): k8s_operator_config_pb.kube_namespace = k8s_config.get( 'kube_namespace') if k8s_config.get('replicas'): k8s_operator_config_pb.replicas = k8s_config.get('replicas') if k8s_config.get('service_name'): k8s_operator_config_pb.service_name = k8s_config.get( 'service_name') if k8s_config.get('service_type'): k8s_operator_config_pb.service_type = k8s_config.get( 'service_type') else: raise BentoMLException( 'Custom deployment is not supported in the current version of BentoML' ) return deployment_pb
def apply(self, deployment_pb, yatai_service, prev_deployment=None): try: ensure_docker_available_or_raise() deployment_spec = deployment_pb.spec sagemaker_config = deployment_spec.sagemaker_operator_config if sagemaker_config is None: raise BentoMLDeploymentException('Sagemaker configuration is missing.') bento_pb = yatai_service.GetBento( GetBentoRequest( bento_name=deployment_spec.bento_name, bento_version=deployment_spec.bento_version, ) ) if bento_pb.bento.uri.type != BentoUri.LOCAL: raise BentoMLException( 'BentoML currently only support local repository' ) else: bento_path = bento_pb.bento.uri.uri ensure_deploy_api_name_exists_in_bento( [api.name for api in bento_pb.bento.bento_service_metadata.apis], [sagemaker_config.api_name], ) sagemaker_client = boto3.client('sagemaker', sagemaker_config.region) with TempDirectory() as temp_dir: sagemaker_project_dir = os.path.jon( temp_dir, deployment_spec.bento_name ) init_sagemaker_project(sagemaker_project_dir, bento_path) ecr_image_path = create_push_docker_image_to_ecr( deployment_spec.bento_name, deployment_spec.bento_version, sagemaker_project_dir, ) execution_role_arn = get_arn_role_from_current_aws_user() model_name = create_sagemaker_model_name( deployment_spec.bento_name, deployment_spec.bento_version ) sagemaker_model_info = { "ModelName": model_name, "PrimaryContainer": { "ContainerHostname": model_name, "Image": ecr_image_path, "Environment": { "API_NAME": sagemaker_config.api_name, "BENTO_SERVER_TIMEOUT": config().get( 'apiserver', 'default_timeout' ), "BENTO_SERVER_WORKERS": config().get( 'apiserver', 'default_gunicorn_workers_count' ), }, }, "ExecutionRoleArn": execution_role_arn, } logger.info("Creating sagemaker model %s", model_name) try: create_model_response = sagemaker_client.create_model( **sagemaker_model_info ) logger.debug("AWS create model response: %s", create_model_response) except ClientError as e: status = _parse_aws_client_exception_or_raise(e) status.error_message = ( 'Failed to create model for SageMaker Deployment: %s', status.error_message, ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) production_variants = [ { "VariantName": generate_aws_compatible_string( deployment_spec.bento_name ), "ModelName": model_name, "InitialInstanceCount": sagemaker_config.instance_count, "InstanceType": sagemaker_config.instance_type, } ] endpoint_config_name = create_sagemaker_endpoint_config_name( deployment_spec.bento_name, deployment_spec.bento_version ) logger.info( "Creating Sagemaker endpoint %s configuration", endpoint_config_name ) try: create_config_response = sagemaker_client.create_endpoint_config( EndpointConfigName=endpoint_config_name, ProductionVariants=production_variants, ) logger.debug( "AWS create endpoint config response: %s", create_config_response ) except ClientError as e: # create endpoint failed, will remove previously created model cleanup_model_error = _cleanup_sagemaker_model( sagemaker_client, deployment_spec.bento_name, deployment_spec.bento_version, ) if cleanup_model_error: cleanup_model_error.error_message = ( 'Failed to clean up model after unsuccessfully ' 'create endpoint config: %s', cleanup_model_error.error_message, ) return ApplyDeploymentResponse( status=cleanup_model_error, deployment=deployment_pb ) status = _parse_aws_client_exception_or_raise(e) status.error_message = ( 'Failed to create endpoint config for SageMaker deployment: %s', status.error_message, ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) endpoint_name = generate_aws_compatible_string( deployment_pb.namespace + '-' + deployment_spec.bento_name ) try: if prev_deployment: logger.debug("Updating sagemaker endpoint %s", endpoint_name) update_endpoint_response = sagemaker_client.update_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name, ) logger.debug( "AWS update endpoint response: %s", update_endpoint_response ) else: logger.debug("Creating sagemaker endpoint %s", endpoint_name) create_endpoint_response = sagemaker_client.create_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name, ) logger.debug( "AWS create endpoint response: %s", create_endpoint_response ) except ClientError as e: # create/update endpoint failed, will remove previously created config # and then remove the model cleanup_endpoint_config_error = _cleanup_sagemaker_endpoint_config( client=sagemaker_client, name=deployment_spec.bento_name, version=deployment_spec.bento_version, ) if cleanup_endpoint_config_error: cleanup_endpoint_config_error.error_message = ( 'Failed to clean up endpoint config after unsuccessfully ' 'apply SageMaker deployment: %s', cleanup_endpoint_config_error.error_message, ) return ApplyDeploymentResponse( status=cleanup_endpoint_config_error, deployment=deployment_pb ) cleanup_model_error = _cleanup_sagemaker_model( client=sagemaker_client, name=deployment_spec.bento_name, version=deployment_spec.bento_version, ) if cleanup_model_error: cleanup_model_error.error_message = ( 'Failed to clean up model after unsuccessfully ' 'apply SageMaker deployment: %s', cleanup_model_error.error_message, ) return ApplyDeploymentResponse( status=cleanup_model_error, deployment=deployment_pb ) status = _parse_aws_client_exception_or_raise(e) status.error_message = ( 'Failed to apply SageMaker deployment: %s', status.error_message, ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) return ApplyDeploymentResponse( status=Status.OK(), deployment=res_deployment_pb ) except BentoMLException as error: return ApplyDeploymentResponse(status=exception_to_return_status(error))
def deployment_dict_to_pb(deployment_dict): deployment_pb = Deployment() if deployment_dict.get('spec'): spec_dict = deployment_dict.get('spec') else: raise YataiDeploymentException( '"spec" is required field for deployment') platform = spec_dict.get('operator') if platform is not None: # converting platform parameter to DeploymentOperator name in proto # e.g. 'aws-lambda' to 'AWS_LAMBDA' deployment_pb.spec.operator = DeploymentSpec.DeploymentOperator.Value( platform.replace('-', '_').upper()) for field in ['name', 'namespace']: if deployment_dict.get(field): deployment_pb.__setattr__(field, deployment_dict.get(field)) if deployment_dict.get('labels') is not None: deployment_pb.labels.update(deployment_dict.get('labels')) if deployment_dict.get('annotations') is not None: deployment_pb.annotations.update(deployment_dict.get('annotations')) if spec_dict.get('bento_name'): deployment_pb.spec.bento_name = spec_dict.get('bento_name') if spec_dict.get('bento_version'): deployment_pb.spec.bento_version = spec_dict.get('bento_version') if deployment_pb.spec.operator == DeploymentSpec.AWS_SAGEMAKER: sagemaker_config = spec_dict.get('sagemaker_operator_config', {}) sagemaker_config_pb = deployment_pb.spec.sagemaker_operator_config for field in [ 'region', 'api_name', 'instance_type', 'num_of_gunicorn_workers_per_instance', ]: if sagemaker_config.get(field): sagemaker_config_pb.__setattr__(field, sagemaker_config.get(field)) if sagemaker_config.get('instance_count'): sagemaker_config_pb.instance_count = int( sagemaker_config.get('instance_count')) elif deployment_pb.spec.operator == DeploymentSpec.AWS_LAMBDA: lambda_conf = spec_dict.get('aws_lambda_operator_config', {}) for field in ['region', 'api_name', 'memory_size', 'timeout']: if lambda_conf.get(field): deployment_pb.spec.aws_lambda_operator_config.__setattr__( field, lambda_conf.get(field)) elif deployment_pb.spec.operator == DeploymentSpec.KUBERNETES: k8s_config = spec_dict.get('kubernetes_operator_config', {}) for field in [ 'kube_namespace', 'replicas', 'service_name', 'service_type' ]: if k8s_config.get(field): deployment_pb.spec.kubernetes_operator_config.__setattr__( field, k8s_config.get(field)) else: raise InvalidArgument( 'Platform "{}" is not supported in the current version of ' 'BentoML'.format(platform)) return deployment_pb
def apply(self, deployment_pb, repo, prev_deployment=None): deployment_spec = deployment_pb.spec sagemaker_config = deployment_spec.sagemaker_operator_config if sagemaker_config is None: raise BentoMLDeploymentException('Sagemaker configuration is missing.') archive_path = repo.get( deployment_spec.bento_name, deployment_spec.bento_version ) # config = load_bentoml_config(bento_path)... sagemaker_client = boto3.client('sagemaker', sagemaker_config.region) with TemporarySageMakerContent( archive_path, deployment_spec.bento_name, deployment_spec.bento_version ) as temp_path: ecr_image_path = create_push_image_to_ecr( deployment_spec.bento_name, deployment_spec.bento_version, temp_path ) execution_role_arn = get_arn_role_from_current_user() model_name = create_sagemaker_model_name( deployment_spec.bento_name, deployment_spec.bento_version ) sagemaker_model_info = { "ModelName": model_name, "PrimaryContainer": { "ContainerHostname": model_name, "Image": ecr_image_path, "Environment": { "API_NAME": sagemaker_config.api_name, "BENTO_SERVER_TIMEOUT": config().get( 'apiserver', 'default_timeout' ), "BENTO_SERVER_WORKERS": config().get( 'apiserver', 'default_gunicorn_workers_count' ), }, }, "ExecutionRoleArn": execution_role_arn, } logger.info("Creating sagemaker model %s", model_name) create_model_response = sagemaker_client.create_model(**sagemaker_model_info) logger.debug("AWS create model response: %s", create_model_response) production_variants = [ { "VariantName": generate_aws_compatible_string( deployment_spec.bento_name ), "ModelName": model_name, "InitialInstanceCount": sagemaker_config.instance_count, "InstanceType": sagemaker_config.instance_type, } ] endpoint_config_name = create_sagemaker_endpoint_config_name( deployment_spec.bento_name, deployment_spec.bento_version ) logger.info( "Creating Sagemaker endpoint %s configuration", endpoint_config_name ) create_endpoint_config_response = sagemaker_client.create_endpoint_config( EndpointConfigName=endpoint_config_name, ProductionVariants=production_variants, ) logger.debug( "AWS create endpoint config response: %s", create_endpoint_config_response ) endpoint_name = generate_aws_compatible_string( deployment_pb.namespace + '-' + deployment_spec.bento_name ) if prev_deployment: logger.info("Updating sagemaker endpoint %s", endpoint_name) update_endpoint_response = sagemaker_client.update_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name ) logger.debug("AWS update endpoint response: %s", update_endpoint_response) else: logger.info("Creating sagemaker endpoint %s", endpoint_name) create_endpoint_response = sagemaker_client.create_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name ) logger.debug("AWS create endpoint response: %s", create_endpoint_response) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)
def _apply( self, deployment_pb, bento_pb, yatai_service, bento_path, prev_deployment=None ): if loader._is_remote_path(bento_path): with loader._resolve_remote_bundle_path(bento_path) as local_path: return self._apply( deployment_pb, bento_pb, yatai_service, local_path, prev_deployment ) deployment_spec = deployment_pb.spec sagemaker_config = deployment_spec.sagemaker_operator_config ensure_deploy_api_name_exists_in_bento( [api.name for api in bento_pb.bento.bento_service_metadata.apis], [sagemaker_config.api_name], ) sagemaker_client = boto3.client('sagemaker', sagemaker_config.region) with TempDirectory() as temp_dir: sagemaker_project_dir = os.path.join(temp_dir, deployment_spec.bento_name) init_sagemaker_project(sagemaker_project_dir, bento_path) ecr_image_path = create_push_docker_image_to_ecr( sagemaker_config.region, deployment_spec.bento_name, deployment_spec.bento_version, sagemaker_project_dir, ) try: model_name = _create_sagemaker_model( sagemaker_client, deployment_spec.bento_name, deployment_spec.bento_version, ecr_image_path, sagemaker_config.api_name, ) except ClientError as e: status = _parse_aws_client_exception(e) status.error_message = ( 'Failed to create model for SageMaker' ' Deployment: {}'.format(status.error_message) ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) try: endpoint_config_name = _create_sagemaker_endpoint_config( sagemaker_client, model_name, deployment_spec.bento_name, deployment_spec.bento_version, sagemaker_config, ) except ClientError as e: # create endpoint failed, will remove previously created model cleanup_model_error = _cleanup_sagemaker_model( sagemaker_client, deployment_spec.bento_name, deployment_spec.bento_version, ) if cleanup_model_error: cleanup_model_error.error_message = ( 'Failed to clean up model after unsuccessfully ' 'create endpoint config: %s', cleanup_model_error.error_message, ) return ApplyDeploymentResponse( status=cleanup_model_error, deployment=deployment_pb ) status = _parse_aws_client_exception(e) status.error_message = ( 'Failed to create endpoint config for SageMaker deployment: %s', status.error_message, ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) endpoint_name = generate_aws_compatible_string( deployment_pb.namespace + '-' + deployment_spec.bento_name ) try: if prev_deployment: logger.debug("Updating sagemaker endpoint %s", endpoint_name) update_endpoint_response = sagemaker_client.update_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name ) logger.debug( "AWS update endpoint response: %s", update_endpoint_response ) else: logger.debug("Creating sagemaker endpoint %s", endpoint_name) create_endpoint_response = sagemaker_client.create_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name ) logger.debug( "AWS create endpoint response: %s", create_endpoint_response ) except ClientError as e: # create/update endpoint failed, will remove previously created config # and then remove the model cleanup_endpoint_config_error = _cleanup_sagemaker_endpoint_config( client=sagemaker_client, name=deployment_spec.bento_name, version=deployment_spec.bento_version, ) if cleanup_endpoint_config_error: cleanup_endpoint_config_error.error_message = ( 'Failed to clean up endpoint config after unsuccessfully ' 'apply SageMaker deployment: %s', cleanup_endpoint_config_error.error_message, ) return ApplyDeploymentResponse( status=cleanup_endpoint_config_error, deployment=deployment_pb ) cleanup_model_error = _cleanup_sagemaker_model( client=sagemaker_client, name=deployment_spec.bento_name, version=deployment_spec.bento_version, ) if cleanup_model_error: cleanup_model_error.error_message = ( 'Failed to clean up model after unsuccessfully apply ' 'SageMaker deployment: {}'.format(cleanup_model_error.error_message) ) return ApplyDeploymentResponse( status=cleanup_model_error, deployment=deployment_pb ) status = _parse_aws_client_exception(e) status.error_message = 'Failed to apply SageMaker ' 'deployment: {}'.format( status.error_message ) return ApplyDeploymentResponse(status=status, deployment=deployment_pb) res_deployment_pb = Deployment(state=DeploymentState()) res_deployment_pb.CopyFrom(deployment_pb) return ApplyDeploymentResponse(status=Status.OK(), deployment=res_deployment_pb)