Skip to content

Commit

Permalink
Deploy to AWS Sagemaker (#134)
Browse files Browse the repository at this point in the history
* Add aws-sagemaker as new platform for deployment

* add nginx and serve script for sagemaker

* add api-name option

* use sagemaker standard /opt/program for model dir

* add base deployment class
  • Loading branch information
yubozhao committed May 16, 2019
1 parent 70de3c6 commit 8b70ae8
Show file tree
Hide file tree
Showing 11 changed files with 647 additions and 47 deletions.
5 changes: 4 additions & 1 deletion bentoml/archive/archiver.py
Expand Up @@ -28,7 +28,8 @@
from bentoml.utils.tempdir import TempDirectory
from bentoml.archive.py_module_utils import copy_used_py_modules
from bentoml.archive.templates import BENTO_MODEL_SETUP_PY_TEMPLATE, \
MANIFEST_IN_TEMPLATE, BENTO_SERVICE_DOCKERFILE_CPU_TEMPLATE, INIT_PY_TEMPLATE
MANIFEST_IN_TEMPLATE, BENTO_SERVICE_DOCKERFILE_CPU_TEMPLATE, \
BENTO_SERVICE_DOCKERFILE_SAGEMAKER_TEMPLATE, INIT_PY_TEMPLATE
from bentoml.archive.config import BentoArchiveConfig

DEFAULT_BENTO_ARCHIVE_DESCRIPTION = """\
Expand Down Expand Up @@ -146,6 +147,8 @@ def _save(bento_service, dst, version=None):
# write Dockerfile
with open(os.path.join(path, 'Dockerfile'), 'w') as f:
f.write(BENTO_SERVICE_DOCKERFILE_CPU_TEMPLATE)
with open(os.path.join(path, 'Dockerfile-sagemaker'), 'w') as f:
f.write(BENTO_SERVICE_DOCKERFILE_SAGEMAKER_TEMPLATE)

# write bentoml.yml
config = BentoArchiveConfig()
Expand Down
30 changes: 30 additions & 0 deletions bentoml/archive/templates.py
Expand Up @@ -99,6 +99,36 @@ def _parse_requirements(file_path):
CMD ["bentoml serve-gunicorn /bento"]
"""

BENTO_SERVICE_DOCKERFILE_SAGEMAKER_TEMPLATE = """\
FROM continuumio/miniconda3
EXPOSE 8080
RUN set -x \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y libpq-dev build-essential\
&& apt-get install -y nginx \
&& rm -rf /var/lib/apt/lists/*
# update conda and setup environment and pre-install common ML libraries to speed up docker build
RUN conda update conda -y \
&& conda install pip numpy scipy \
&& pip install gunicorn six gevent
# copy over model files
COPY . /opt/program
WORKDIR /opt/program
# update conda base env
RUN conda env update -n base -f /opt/program/environment.yml
RUN pip install -r /opt/program/requirements.txt
# run user defined setup script
RUN if [ -f /opt/program/setup.sh ]; then /bin/bash -c /opt/program/setup.sh; fi
ENV PATH="/opt/program:${PATH}"
"""

INIT_PY_TEMPLATE = """\
import os
import sys
Expand Down
80 changes: 56 additions & 24 deletions bentoml/cli/__init__.py
Expand Up @@ -21,16 +21,36 @@
import json
import click

from enum import Enum

from bentoml.archive import load
from bentoml.server import BentoAPIServer
from bentoml.server.bento_sagemaker_server import BentoSagemakerServer
from bentoml.server.gunicorn_server import GunicornApplication, get_gunicorn_worker_count
from bentoml.cli.click_utils import DefaultCommandGroup, conditional_argument
from bentoml.deployment.serverless import ServerlessDeployment
from bentoml.deployment.sagemaker import SagemakerDeployment
from bentoml.utils.exceptions import BentoMLException

SERVERLESS_PLATFORMS = ['aws-lambda', 'aws-lambda-py2', 'gcp-function']


class CLI_MESSAGE_TYPE(Enum):
SUCCESS = 1
ERROR = 2


def display_bentoml_cli_message(message, message_type=CLI_MESSAGE_TYPE.SUCCESS):
if message_type == CLI_MESSAGE_TYPE.SUCCESS:
color = 'green'
elif message_type == CLI_MESSAGE_TYPE.ERROR:
color = 'red'
else:
color = 'green'
click.echo('BentoML: ', nl=False)
click.secho(message, fg=color)


def create_bentoml_cli(installed_archive_path=None):
# pylint: disable=unused-variable

Expand Down Expand Up @@ -128,20 +148,23 @@ def cli():
]), required=True)
@click.option('--region', type=click.STRING)
@click.option('--stage', type=click.STRING)
def deploy(archive_path, platform, region, stage):
@click.option('--api-name', type=click.STRING)
@click.option('--instance-type', type=click.STRING)
@click.option('--instance-count', type=click.INT)
def deploy(archive_path, platform, region, stage, api_name, instance_type, instance_count):
if platform in SERVERLESS_PLATFORMS:
deployment = ServerlessDeployment(platform, archive_path, region, stage)
deployment = ServerlessDeployment(archive_path, platform, region, stage)
elif platform == 'aws-sagemaker':
deployment = SagemakerDeployment(archive_path, api_name, region, instance_count,
instance_type)
else:
raise BentoMLException(
'Deploying with "--platform=%s" is not supported ' % platform +
'in the current version of BentoML'
)

raise BentoMLException('Deploying with "--platform=%s" is not supported ' % platform +
'in the current version of BentoML')
output_path = deployment.deploy()
click.echo('BentoML: ', nl=False)
click.secho('Deploy to {platform} complete!'.format(platform=platform), fg='green')
click.secho('Deployment archive is saved at {output_path}'.format(output_path=output_path),
fg='green')

display_bentoml_cli_message('Deploy to {platform} complete!'.format(platform=platform))
display_bentoml_cli_message(
'Deployment archive is saved at {output_path}'.format(output_path=output_path))
return

# Example usage: bentoml delete-deployment ARCHIVE_PATH --platform=aws-lambda
Expand All @@ -151,16 +174,24 @@ def deploy(archive_path, platform, region, stage):
'aws-lambda', 'aws-lambda-py2', 'gcp-function', 'aws-sagemaker', 'azure-ml', 'algorithmia'
]), required=True)
@click.option('--region', type=click.STRING, required=True)
@click.option('--api-name', type=click.STRING)
@click.option('--stage', type=click.STRING)
def delete_deployment(archive_path, platform, region, stage):
def delete_deployment(archive_path, platform, region, stage, api_name):
if platform in SERVERLESS_PLATFORMS:
deployment = ServerlessDeployment(platform, archive_path, region, stage)
deployment = ServerlessDeployment(archive_path, platform, region, stage)
elif platform == 'aws-sagemaker':
deployment = SagemakerDeployment(archive_path, api_name, region)
else:
raise BentoMLException('Remove deployment with --platform=%s' % platform +
'is not supported in the current version of BentoML')
result = deployment.delete()
if result:
display_bentoml_cli_message(
'Delete {platform} deployment successful'.format(platform=platform))
else:
raise BentoMLException(
'Remove deployment with --platform=%s' % platform +
'is not supported in the current version of BentoML'
)
deployment.delete()
display_bentoml_cli_message(
'Delete {platform} deployment unsuccessful'.format(platform=platform),
CLI_MESSAGE_TYPE.ERROR)
return

# Example usage: bentoml check-deployment-status ARCHIVE_PATH --platform=aws-lambda
Expand All @@ -171,14 +202,15 @@ def delete_deployment(archive_path, platform, region, stage):
]), required=True)
@click.option('--region', type=click.STRING, required=True)
@click.option('--stage', type=click.STRING)
def check_deployment_status(archive_path, platform, region, stage):
@click.option('--api-name', type=click.STRING)
def check_deployment_status(archive_path, platform, region, stage, api_name):
if platform in SERVERLESS_PLATFORMS:
deployment = ServerlessDeployment(platform, archive_path, region, stage)
deployment = ServerlessDeployment(archive_path, platform, region, stage)
elif platform == 'aws-sagemaker':
deployment = SagemakerDeployment(archive_path, api_name, region)
else:
raise BentoMLException(
'check deployment status with --platform=%s' % platform +
'is not supported in the current version of BentoML'
)
raise BentoMLException('check deployment status with --platform=%s' % platform +
'is not supported in the current version of BentoML')

deployment.check_status()
return
Expand Down
52 changes: 52 additions & 0 deletions bentoml/deployment/base_deployment.py
@@ -0,0 +1,52 @@
# BentoML - Machine Learning Toolkit for packaging and deploying models
# Copyright (C) 2019 Atalaya Tech, Inc.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from bentoml.archive import load


class Deployment(object):
"""Deployment is spec for describing what actions deployment should have
to interact with BentoML cli and BentoML service archive.
"""

def __init__(self, archive_path):
self.bento_service = load(archive_path)
self.archive_path = archive_path

def deploy(self):
"""Deploy bentoml service.
:return: Boolean, True if success
"""
raise NotImplementedError

def check_status(self):
"""Check deployment status
:params
:return: Boolean, True if success Status Message String
"""
raise NotImplementedError

def delete(self):
"""Delete deployment, if deployment is active
:return: Boolean, True if success
"""
raise NotImplementedError

0 comments on commit 8b70ae8

Please sign in to comment.