{ "cells": [ { "cell_type": "markdown", "id": "fd8ab249", "metadata": {}, "source": [ "# Create a custom Studio app image\n", "This notebook implement a process of creating a SageMaker Studio custom app image. The advantage of creating an image and make it available to all SageMaker Studio users is that it creates a consistent environment to use in Studio notebooks. You can also run app images locally which improves reproducability of your workloads.\n", "\n", "For details, hands-on examples, and documentation refer to the following resources:\n", "- [SageMaker Studio developer guide](https://docs.aws.amazon.com/sagemaker/latest/dg/studio-byoi.html)\n", "- [Custom image samples GitHub](https://github.com/aws-samples/sagemaker-studio-custom-image-samples/)\n", "- [Use Studio Container Build CLI](https://github.com/aws/amazon-sagemaker-examples/tree/main/aws_sagemaker_studio/sagemaker_studio_image_build)\n", "- [Using the Amazon SageMaker Studio Image Build CLI to build container images from your Studio notebooks](https://aws.amazon.com/blogs/machine-learning/using-the-amazon-sagemaker-studio-image-build-cli-to-build-container-images-from-your-studio-notebooks/)\n", "\n", "
Run this notebook as a SageMaker notebook instance, not as a Studio notebook, because you cannot run Docker commands in a SageMaker Studio notebook or terminal. Studio environment already runs in a Docker container and you cannot run container in container. \n", "
" ] }, { "cell_type": "markdown", "id": "d499f226", "metadata": {}, "source": [ "## Configure environment" ] }, { "cell_type": "code", "execution_count": 139, "id": "4f8e94f7", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "import boto3\n", "import sagemaker" ] }, { "cell_type": "markdown", "id": "13e54580", "metadata": {}, "source": [ "Put all container files into a separate directory." ] }, { "cell_type": "code", "execution_count": 140, "id": "94894fdf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mkdir: cannot create directory ‘container’: File exists\n" ] } ], "source": [ "!mkdir container" ] }, { "cell_type": "markdown", "id": "9a94d79f", "metadata": {}, "source": [ "Get environment metadata:" ] }, { "cell_type": "code", "execution_count": 141, "id": "3f70d266", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Notebook metadata: {'ResourceArn': 'arn:aws:sagemaker:us-east-1:906545278380:notebook-instance/d2l-notebooks', 'ResourceName': 'd2l-notebooks'}\n" ] } ], "source": [ "NOTEBOOK_METADATA_FILE = \"/opt/ml/metadata/resource-metadata.json\"\n", "\n", "if os.path.exists(NOTEBOOK_METADATA_FILE):\n", " with open(NOTEBOOK_METADATA_FILE, \"rb\") as f:\n", " md = json.loads(f.read())\n", " print(f\"Notebook metadata: {md}\")" ] }, { "cell_type": "code", "execution_count": 142, "id": "06b96352", "metadata": {}, "outputs": [], "source": [ "role = sagemaker.get_execution_role()" ] }, { "cell_type": "code", "execution_count": 143, "id": "d59e3481", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "906545278380.dkr.ecr.us-east-1.amazonaws.com/conda-env-kernel:conda-env-kernel\n" ] } ], "source": [ "repo_name = \"conda-env-kernel\"\n", "image_name = \"conda-env-kernel\"\n", "account_id = boto3.client(\"sts\").get_caller_identity()[\"Account\"]\n", "region = md[\"ResourceArn\"].split(\":\")[3]\n", "full_name = f\"{account_id}.dkr.ecr.{region}.amazonaws.com/{repo_name}:{image_name}\"\n", "\n", "print(full_name)" ] }, { "cell_type": "markdown", "id": "dd19f04b", "metadata": {}, "source": [ "Create a conda environment specification file. The Conda environment must have a Jupyter kernel package installed, for example, `ipykernel` for Python kernel." ] }, { "cell_type": "code", "execution_count": 164, "id": "7e3a77a9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting container/environment.yml\n" ] } ], "source": [ "%%writefile container/environment.yml\n", "name: customenv\n", "channels:\n", " - conda-forge\n", "dependencies:\n", " - python=3.8.*\n", " - ipykernel \n", " - pip\n", " - pip:\n", " - awscli\n", " - boto3\n", " - sagemaker\n", " - scikit-learn==1.0.2\n", " - scipy==1.8.*\n", " - seaborn==0.11.2\n", " - prophet==1.*\n", " - plotly==5.9.*\n", " - prophet==1.*\n", " - metno-locationforecast == 1.*\n", " - meteostat == 1.*\n", " - pandas==1.4.*\n", " - numpy\n", " - matplotlib==3.5.*\n", " - geopandas==0.11.*\n", " - shapely==1.8.*" ] }, { "cell_type": "markdown", "id": "5a275a56", "metadata": {}, "source": [ "Create the second environment specification file:" ] }, { "cell_type": "code", "execution_count": 165, "id": "ce3ae4c0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing container/environment-env2.yml\n" ] } ], "source": [ "%%writefile container/environment-env2.yml\n", "name: env2\n", "channels:\n", " - conda-forge\n", "dependencies:\n", " - python=3.8.*\n", " - numpy\n", " - awscli\n", " - boto3\n", " - ipykernel" ] }, { "cell_type": "markdown", "id": "091a10a4", "metadata": {}, "source": [ "Now create the Dockerfile." ] }, { "cell_type": "code", "execution_count": 169, "id": "651d5f2d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting container/Dockerfile\n" ] } ], "source": [ "%%writefile container/Dockerfile\n", "FROM continuumio/miniconda3:4.10.3\n", "\n", "COPY environment*.yml ./\n", "\n", "RUN conda env create -f environment.yml\n", "RUN conda env create -f environment-env2.yml" ] }, { "cell_type": "markdown", "id": "98df4a15", "metadata": {}, "source": [ "## Build the Docker image" ] }, { "cell_type": "code", "execution_count": 167, "id": "b6ef2e82", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted Containers:\n", "364099ced8e5312cc3f770424ee23adaed74d5e3bc79ecc9d91629b0ead3d5d1\n", "\n", "Deleted Images:\n", "untagged: continuumio/miniconda3:4.10.3\n", "untagged: continuumio/miniconda3@sha256:a137c7da98c8680467490e15ac3c54e25db77495be1737076b053a6790ad6082\n", "untagged: 906545278380.dkr.ecr.us-east-1.amazonaws.com/conda-env-kernel:conda-env-kernel\n", "untagged: 906545278380.dkr.ecr.us-east-1.amazonaws.com/conda-env-kernel@sha256:243a83e68a92d759c076ad44e74c4160ad308797f90c07be60d18ee09b78850e\n", "untagged: conda-env-kernel:latest\n", "deleted: sha256:e75acd6b6e6728592abc629b98ec62c19646b34e72730e57519f6a99151db8d5\n", "deleted: sha256:31054d91b38f14f3804ce9557d7af80a3ca26e498dc57c256acdae80c47c547d\n", "deleted: sha256:5ec60ab893af190ecb5f526f6652824241f1601850020889c179ac62faed86a9\n", "deleted: sha256:7510c895034ffd0735e2467f3f395a715ca8fc6dec16a8ef251732fb9c941f6f\n", "deleted: sha256:83812c01cb604e458a933b7c0dd0b6b4bf153aa42a1460bb785b5239e6fffc7c\n", "deleted: sha256:50db6ae4d0d7f5e746bc16dcff1049f6c7dee837df60e98eb8bb27c64c15719f\n", "deleted: sha256:e6f45bea1520d0ec6815a42028d6e0be84c92bd58c3018d1f909922231701116\n", "deleted: sha256:814bff7343242acfd20a2c841e041dd57c50f0cf844d4abd2329f78b992197f4\n", "\n", "Total reclaimed space: 2.237GB\n" ] } ], "source": [ "!docker system prune -af" ] }, { "cell_type": "markdown", "id": "6b86f03e", "metadata": {}, "source": [ "The following script runs for about 7 minutes." ] }, { "cell_type": "code", "execution_count": null, "id": "6b120b03", "metadata": {}, "outputs": [], "source": [ "%%sh\n", "\n", "cd container\n", "\n", "# Region, defaults to us-east-1\n", "REGION=$(aws configure get region)\n", "REGION=${REGION:-us-east-1}\n", "\n", "ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)\n", "REPO_NAME=conda-env-kernel\n", "IMAGE_NAME=conda-env-kernel\n", "FULL_NAME=\"${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPO_NAME}:${IMAGE_NAME}\"\n", "\n", "# If the repository doesn't exist in ECR, create it.\n", "aws ecr describe-repositories --repository-names \"${REPO_NAME}\" > /dev/null 2>&1\n", "\n", "if [ $? -ne 0 ]\n", "then\n", " aws ecr create-repository --repository-name \"${REPO_NAME}\" > /dev/null\n", "fi\n", "\n", "# Login to ECR\n", "aws --region ${REGION} ecr get-login-password | docker login --username AWS --password-stdin ${FULL_NAME}\n", "\n", "# Build and push the image\n", "docker build . -t ${IMAGE_NAME} -t ${FULL_NAME}\n", "\n", "docker push ${FULL_NAME}" ] }, { "cell_type": "markdown", "id": "16af8c5c", "metadata": {}, "source": [ "List images in the ECR:" ] }, { "cell_type": "code", "execution_count": 148, "id": "bf3e0a5e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"imageIds\": [\n", " {\n", " \"imageDigest\": \"sha256:3cf396f406e91c7c0045158faced7e2c689becf543b51a65d76a24b1bdca3b75\"\n", " },\n", " {\n", " \"imageDigest\": \"sha256:a92c66c029d27cfdf315e58057d981e7cad811cf424c4ad2783ed06fa8bae741\"\n", " },\n", " {\n", " \"imageDigest\": \"sha256:463b0ad96fb187a640a95d45911458b82522f7be4944b18e7357b91a98c8eee1\",\n", " \"imageTag\": \"latest\"\n", " },\n", " {\n", " \"imageDigest\": \"sha256:243a83e68a92d759c076ad44e74c4160ad308797f90c07be60d18ee09b78850e\",\n", " \"imageTag\": \"conda-env-kernel\"\n", " },\n", " {\n", " \"imageDigest\": \"sha256:73eaed71f6982192f4c450b36fc2e048d0faf5606e67b81c5b40a370962b44aa\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "!aws ecr list-images --repository-name {repo_name}" ] }, { "cell_type": "markdown", "id": "329f2b1a", "metadata": {}, "source": [ "## Use the custom app image with SageMaker Studio\n", "Follow the these steps to use your custom image with Studio:\n", "- Create a SageMaker Image (SMI) with the image in ECR\n", "- Create an App Image Version \n", "- Create an AppImageConfig \n", "- Update domain with the AppImageConfig\n", "\n", "❗ Everytime you update the image in ECR, a new image version should be created. See [Update image with SageMaker Studio](#update-image-with-sagemaker-studio).\n", "\n", "This notebook uses `boto3` SDK, but you can also use `aws cli` to run all commands." ] }, { "cell_type": "code", "execution_count": 170, "id": "2402a11e", "metadata": {}, "outputs": [], "source": [ "sm = boto3.client(\"sagemaker\")" ] }, { "cell_type": "markdown", "id": "c1dbbdbc", "metadata": {}, "source": [ "Create an image configuration file `app-image-config-input.json`. SageMaker Studio will automatically recognize the Conda environments as corresponding kernels named `conda-env-customenv-py` and `conda-env-env2-py`. You can create multiple Conda environments and Studio automatically populates the kernel selection dropdown on the **Set up notebook environment** dialog.\n", "\n", "Note the naming convention for `KernelSpecs.Name`. You must specify the kernel name based on the name of your Conda environment: `conda-env--py`." ] }, { "cell_type": "code", "execution_count": 171, "id": "09bc282e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting app-image-config-input.json\n" ] } ], "source": [ "%%writefile app-image-config-input.json\n", "{\n", " \"AppImageConfigName\": \"conda-env-kernel-config\",\n", " \"KernelGatewayImageConfig\": {\n", " \"KernelSpecs\": [\n", " {\n", " \"Name\": \"conda-env-customenv-py\",\n", " \"DisplayName\": \"Python [conda env: customenv]\"\n", " },\n", " {\n", " \"Name\": \"conda-env-env2-py\",\n", " \"DisplayName\": \"Python [conda env: env2]\"\n", " }\n", " ],\n", " \"FileSystemConfig\": {\n", " \"MountPath\": \"/root\",\n", " \"DefaultUid\": 0,\n", " \"DefaultGid\": 0\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 172, "id": "b791e92e", "metadata": {}, "outputs": [], "source": [ "# load app image config as a variable\n", "with open(\"app-image-config-input.json\", \"rb\") as f:\n", " app_image_config = json.loads(f.read())" ] }, { "cell_type": "code", "execution_count": 173, "id": "c6b33862", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'ImageArn': 'arn:aws:sagemaker:us-east-1:906545278380:image/conda-env-kernel',\n", " 'ResponseMetadata': {'RequestId': '0367765a-ea94-4b66-9b05-0787436ba224',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': '0367765a-ea94-4b66-9b05-0787436ba224',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '78',\n", " 'date': 'Thu, 27 Oct 2022 11:27:10 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 173, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create Studio Image\n", "r = sm.create_image(\n", " Description=\"custom conda environment\",\n", " DisplayName=\"Python [conda env: custom_env]\",\n", " ImageName=image_name,\n", " RoleArn=role,\n", ")\n", "\n", "r" ] }, { "cell_type": "code", "execution_count": 174, "id": "717666f9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'ImageVersionArn': 'arn:aws:sagemaker:us-east-1:906545278380:image-version/conda-env-kernel/1',\n", " 'ResponseMetadata': {'RequestId': '2947e55d-46d6-4826-ad31-fc0f4c21a6b2',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': '2947e55d-46d6-4826-ad31-fc0f4c21a6b2',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '95',\n", " 'date': 'Thu, 27 Oct 2022 11:27:13 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 174, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create image version\n", "r = sm.create_image_version(\n", " BaseImage=full_name,\n", " ImageName=image_name,\n", ")\n", "\n", "r" ] }, { "cell_type": "markdown", "id": "ed45986f", "metadata": {}, "source": [ "Check the status of the image version. The status must be `CREATED`. Do not proceed with any other status." ] }, { "cell_type": "code", "execution_count": 175, "id": "e88242d2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Image version status: CREATED\n" ] } ], "source": [ "r = sm.describe_image_version(ImageName=image_name)\n", "print(f\"Image version status: {r['ImageVersionStatus']}\")\n", "assert(r['ImageVersionStatus'] == 'CREATED')" ] }, { "cell_type": "code", "execution_count": 176, "id": "efec311c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'AppImageConfigArn': 'arn:aws:sagemaker:us-east-1:906545278380:app-image-config/conda-env-kernel-config',\n", " 'ResponseMetadata': {'RequestId': '776a53f5-0212-4a1c-bef1-635c4997846d',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': '776a53f5-0212-4a1c-bef1-635c4997846d',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '105',\n", " 'date': 'Thu, 27 Oct 2022 11:27:17 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 176, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create image config\n", "r = sm.create_app_image_config(\n", " AppImageConfigName=app_image_config[\"AppImageConfigName\"],\n", " KernelGatewayImageConfig=app_image_config[\"KernelGatewayImageConfig\"],\n", ")\n", "\n", "r" ] }, { "cell_type": "markdown", "id": "97c3f9cc", "metadata": {}, "source": [ "Update an existing SageMaker domain to use this app image. If you don't have a domain, you must [create a new one](https://docs.aws.amazon.com/sagemaker/latest/dg/onboard-iam.html)." ] }, { "cell_type": "code", "execution_count": 177, "id": "4da4fcc9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting update-domain-input.json\n" ] } ], "source": [ "%%writefile update-domain-input.json\n", "{\n", " \"DefaultUserSettings\": {\n", " \"KernelGatewayAppSettings\": {\n", " \"CustomImages\": [\n", " {\n", " \"ImageName\": \"conda-env-kernel\",\n", " \"AppImageConfigName\": \"conda-env-kernel-config\"\n", " }\n", " ]\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 178, "id": "40f6c409", "metadata": {}, "outputs": [], "source": [ "with open(\"update-domain-input.json\", \"rb\") as f:\n", " update_domain_input = json.loads(f.read())" ] }, { "cell_type": "code", "execution_count": 179, "id": "9a8b5733", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "d-r8pbvl3oamh6\n" ] } ], "source": [ "# Get domain_id for the active domain\n", "r = [d for d in sm.list_domains()[\"Domains\"] if d[\"Status\"] == 'InService']\n", "if not len(r):\n", " raise Exception(\"You don't have any active domain, create a new one!\")\n", " \n", "if len(r) > 1:\n", " raise Exception(\"You have more than one active domain, please manually select one for update!\")\n", " \n", "domain_id = r[0][\"DomainId\"]\n", "\n", "print(domain_id)" ] }, { "cell_type": "code", "execution_count": 180, "id": "dd26fcb9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'DomainArn': 'arn:aws:sagemaker:us-east-1:906545278380:domain/d-r8pbvl3oamh6',\n", " 'ResponseMetadata': {'RequestId': '10a10cff-1f31-4a50-8684-8d26fbda4d16',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': '10a10cff-1f31-4a50-8684-8d26fbda4d16',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '78',\n", " 'date': 'Thu, 27 Oct 2022 11:27:43 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 180, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r = sm.update_domain(\n", " DomainId=domain_id,\n", " DefaultUserSettings=update_domain_input[\"DefaultUserSettings\"],\n", ")\n", "\n", "r" ] }, { "cell_type": "markdown", "id": "ad3bba76", "metadata": {}, "source": [ "There is an about 5 min delay from when a custom image is attached to the domain until the app is visible in the dropdown in **Set up notebook environment**." ] }, { "cell_type": "markdown", "id": "d6027012", "metadata": {}, "source": [ "## Start a notebook witht the custom app" ] }, { "cell_type": "markdown", "id": "3d562e6d", "metadata": {}, "source": [ "Start SageMaker Studio, open a new notebook and select your new custom app `conda-env-kernel`:\n", "![](../img/select-kernel.png)" ] }, { "cell_type": "markdown", "id": "b63a478b", "metadata": {}, "source": [ "If your app image contains more than one Conda environments, Studio shows these enviroments as selectable kernels in the **Kernel** dropdown:\n", "\n", "![](../img/select-kernel-2.png)" ] }, { "cell_type": "markdown", "id": "df913f9f", "metadata": {}, "source": [ "After you selected a kernel, Studio automatically activates the corresponding Conda environment in the notebook. You can check in which environment is active with `%conda env list` command in the notebook:\n", "\n", "Kernel `customenv`:\n", "\n", "![](../img/conda-list-1.png)\n", "\n", "Kernel `env2`:\n", "\n", "![](../img/conda-list-2.png)" ] }, { "cell_type": "markdown", "id": "c4cc0ecb", "metadata": {}, "source": [ "To see the kernels that are installed on the notebook, run this command in the Studio notebook:" ] }, { "cell_type": "code", "execution_count": 91, "id": "b16d29c4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ListKernelSpecs] WARNING | Config option `kernel_spec_manager_class` not recognized by `ListKernelSpecs`.\n", "Available kernels:\n", " ir /home/ec2-user/.local/share/jupyter/kernels/ir\n", " python3 /home/ec2-user/anaconda3/envs/pytorch_p38/share/jupyter/kernels/python3\n" ] } ], "source": [ "!jupyter-kernelspec list" ] }, { "cell_type": "markdown", "id": "afc62d9c", "metadata": {}, "source": [ "## Update image with SageMaker Studio\n", "To update a custom app image in SageMaker domain, for example with a new version of image, follow these steps:\n", "- Build and push a new version of the image to ECR\n", "- Create a new App Image Version\n", "- Re-create App in SageMaker Studio" ] }, { "cell_type": "code", "execution_count": 67, "id": "b7c9126b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'906545278380.dkr.ecr.us-east-1.amazonaws.com/conda-env-kernel:conda-env-kernel'" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "full_name" ] }, { "cell_type": "code", "execution_count": 68, "id": "fe85ff8f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'ImageVersionArn': 'arn:aws:sagemaker:us-east-1:906545278380:image-version/conda-env-kernel/3',\n", " 'ResponseMetadata': {'RequestId': 'b8ff92ca-98bc-4797-8b2b-18c7aa2feac2',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'x-amzn-requestid': 'b8ff92ca-98bc-4797-8b2b-18c7aa2feac2',\n", " 'content-type': 'application/x-amz-json-1.1',\n", " 'content-length': '95',\n", " 'date': 'Wed, 26 Oct 2022 16:31:01 GMT'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r = sm.create_image_version(\n", " BaseImage=full_name,\n", " ImageName=image_name,\n", ")\n", "\n", "r" ] }, { "cell_type": "code", "execution_count": 69, "id": "e878438f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Image version status: CREATED\n" ] } ], "source": [ "r = sm.describe_image_version(ImageName=image_name)\n", "print(f\"Image version status: {r['ImageVersionStatus']}\")\n", "assert(r['ImageVersionStatus'] == 'CREATED')" ] }, { "cell_type": "markdown", "id": "cccf0939", "metadata": {}, "source": [ "Delete your custom app in Studio and restart all associated kernels. You can delete the app either in Studio UX:\n", "\n", "![](../img/delete-app-studio.png)\n", "\n", "or in AWS console:\n", "\n", "![](../img/delete-app-console.png)\n", "\n", "After you deleted the custom app, re-open a notebook and select the custom image in the **Set up notebook environment** dialog." ] }, { "cell_type": "markdown", "id": "196a00e7", "metadata": {}, "source": [ "## Local testing\n", "Before attach an app image to a SageMaker domain, you can debug and test your app locally.\n", "\n", "Run the following commands in the terminal." ] }, { "cell_type": "code", "execution_count": null, "id": "5ba592a3", "metadata": {}, "outputs": [], "source": [ "IMAGE_NAME=\"conda-env-kernel\"\n", "docker run -it \"$IMAGE_NAME\" bash" ] }, { "cell_type": "markdown", "id": "07bbae9f", "metadata": {}, "source": [ "For Conda-based images activate an environment:" ] }, { "cell_type": "code", "execution_count": null, "id": "18b711e0", "metadata": {}, "outputs": [], "source": [ "conda activate customenv" ] }, { "cell_type": "markdown", "id": "3febcd20", "metadata": {}, "source": [ "In the running container list the available kernels:" ] }, { "cell_type": "code", "execution_count": null, "id": "bb1c4391", "metadata": {}, "outputs": [], "source": [ "jupyter-kernelspec list" ] }, { "cell_type": "markdown", "id": "d82c847f", "metadata": {}, "source": [ "If no kernel or `jupyter-kernelspec` is not available, install `ipykernel`:" ] }, { "cell_type": "code", "execution_count": null, "id": "ca0cbff8", "metadata": {}, "outputs": [], "source": [ "pip install ipykernel\n", "python -m ipykernel install --sys-prefix" ] }, { "cell_type": "markdown", "id": "193f8c79", "metadata": {}, "source": [ "You must also add `pip install ipykernel` to the Docker file if you don't use Conda environments." ] }, { "cell_type": "code", "execution_count": null, "id": "ac7f2c4f", "metadata": {}, "outputs": [], "source": [ "RUN pip install ipykernel && \\\n", " python -m ipykernel install --sys-prefix" ] }, { "cell_type": "markdown", "id": "19dc2dbf", "metadata": {}, "source": [ "Refer to this [sample notebook](https://github.com/aws-samples/sagemaker-studio-custom-image-samples/blob/main/DEVELOPMENT.md) for more details." ] }, { "cell_type": "markdown", "id": "4cc0be53", "metadata": {}, "source": [ "## Clean up\n", "To avoid charges you must stop all active SageMaker notebook instances. Follow the [clean up instructions](https://docs.aws.amazon.com/sagemaker/latest/dg/ex1-cleanup.html) in the Developer Guide." ] }, { "cell_type": "markdown", "id": "812343ac", "metadata": {}, "source": [ "## Resources\n", "- [Available Amazon SageMaker Images](https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks-available-images.html)\n", "- [Developer guide for BYOI](https://docs.aws.amazon.com/sagemaker/latest/dg/studio-byoi.html)\n", "- [Custom SageMaker image specifications](https://docs.aws.amazon.com/sagemaker/latest/dg/studio-byoi-specs.html)\n", "- [Conda environments as kernels](https://github.com/aws-samples/sagemaker-studio-custom-image-samples/tree/main/examples/conda-env-kernel-image)\n", "- [Custom app image samples GitHub](https://github.com/aws-samples/sagemaker-studio-custom-image-samples/)\n", "- [How to use Studio Container Build CLI](https://github.com/aws/amazon-sagemaker-examples/tree/main/aws_sagemaker_studio/sagemaker_studio_image_build)\n", "- [Using the Amazon SageMaker Studio Image Build CLI to build container images from your Studio notebooks](https://aws.amazon.com/blogs/machine-learning/using-the-amazon-sagemaker-studio-image-build-cli-to-build-container-images-from-your-studio-notebooks/)\n", "- [Create a new Conda environment in Sagemaker home directory (Amazon EFS)](https://github.com/durgasury/efs_backed_conda)\n", "- [Automating the Setup of SageMaker Studio Custom Images](https://towardsdatascience.com/automating-the-setup-of-sagemaker-studio-custom-images-4a3433fd7148)\n", "- [Activating a Conda environment in your Dockerfile](https://pythonspeed.com/articles/activate-conda-dockerfile/)" ] }, { "cell_type": "code", "execution_count": null, "id": "9a06db2e", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "instance_type": "ml.t3.medium", "kernelspec": { "display_name": "Python 3 (Data Science)", "language": "python", "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/datascience-1.0" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.10" }, "vscode": { "interpreter": { "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" } } }, "nbformat": 4, "nbformat_minor": 5 }