aws / aws/amazon-sagemaker-feedback

SageMaker Studio Docker support does not support un-versioned Docker APIs

Open
#161 2 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
No language data
Stars
10
Forks
3
PR merge metrics
No merged PRs in 30d

Description

### Product Version

- [ ] Amazon SageMaker Studio Classic
- [x] Amazon SageMaker Studio
- [ ] Issue is not related to SageMaker Studio

### Issue Description

In SageMaker Studio I am attempting to build a Docker image for a registered MLFlow model as follows:
```
import mlflow
mlflow.set_tracking_uri()
mlflow.models.build_docker("models://")
```

The MLFlow [docker-utils](https://github.com/mlflow/mlflow/blob/master/mlflow/models/docker_utils.py#L215) attempts to retrieve the Docker version in order to determine if the `--platform` flag is supported. This call is delegated to [docker-py](https://github.com/docker/docker-py) to retrieve the version, which attempts to access this information via the local Docker API `/version` path. However, in SageMaker Studio that path returns a HTTP 301. This is in contrast to the specific version embedded in the path, e.g. `v1.41/version`, which works.

While the [documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/studio-updated-local-get-started.html#studio-updated-local-docker-installation) mentions that Studio only supports Docker version `20.10.X` and Docker Engine API version `1.41`, client libraries are not aware of this information when trying to resolve the version.

### Expected Behavior

The bare `/version` path is supported and returns the same information as the version-specific paths, such as `v1.41/version`.

Example from machine outside of SageMaker Studio:
```
$ curl -s --unix-sock /var/run/docker.sock http://localhost:2376/version | jq
{
"Platform": {
"Name": ""
},
"Components": [
{
"Name": "Engine",
"Version": "25.0.6",
"Details": {
"ApiVersion": "1.44",
"Arch": "amd64",
"BuildTime": "2024-12-03T05:03:35.000000000+00:00",
"Experimental": "false",
"GitCommit": "b08a51f",
"GoVersion": "go1.22.7",
"KernelVersion": "5.10.230-202.885.amzn2int.x86_64",
"MinAPIVersion": "1.24",
"Os": "linux"
}
},
{
"Name": "containerd",
"Version": "1.7.23",
"Details": {
"GitCommit": "57f17b0a6295a39009d861b89e3b3b87b005ca27"
}
},
{
"Name": "runc",
"Version": "1.1.14",
"Details": {
"GitCommit": "2c9f5602f0ba3d9da1c2596322dfc4e156844890"
}
},
{
"Name": "docker-init",
"Version": "0.19.0",
"Details": {
"GitCommit": "de40ad0"
}
}
],
"Version": "25.0.6",
"ApiVersion": "1.44",
"MinAPIVersion": "1.24",
"GitCommit": "b08a51f",
"GoVersion": "go1.22.7",
"Os": "linux",
"Arch": "amd64",
"KernelVersion": "5.10.230-202.885.amzn2int.x86_64",
"BuildTime": "2024-12-03T05:03:35.000000000+00:00"
}

$ curl -s --unix-sock /var/run/docker.sock http://localhost:2376/v1.41/version | jq
{
"Platform": {
"Name": ""
},
"Components": [
{
"Name": "Engine",
"Version": "25.0.6",
"Details": {
"ApiVersion": "1.44",
"Arch": "amd64",
"BuildTime": "2024-12-03T05:03:35.000000000+00:00",
"Experimental": "false",
"GitCommit": "b08a51f",
"GoVersion": "go1.22.7",
"KernelVersion": "5.10.230-202.885.amzn2int.x86_64",
"MinAPIVersion": "1.24",
"Os": "linux"
}
},
{
"Name": "containerd",
"Version": "1.7.23",
"Details": {
"GitCommit": "57f17b0a6295a39009d861b89e3b3b87b005ca27"
}
},
{
"Name": "runc",
"Version": "1.1.14",
"Details": {
"GitCommit": "2c9f5602f0ba3d9da1c2596322dfc4e156844890"
}
},
{
"Name": "docker-init",
"Version": "0.19.0",
"Details": {
"GitCommit": "de40ad0"
}
}
],
"Version": "25.0.6",
"ApiVersion": "1.44",
"MinAPIVersion": "1.24",
"GitCommit": "b08a51f",
"GoVersion": "go1.22.7",
"Os": "linux",
"Arch": "amd64",
"KernelVersion": "5.10.230-202.885.amzn2int.x86_64",
"BuildTime": "2024-12-03T05:03:35.000000000+00:00"
}
```

### Observed Behavior

The bare `/version` path returns a HTTP 301 Moved Permanently response, which cannot be parsed.

```
sagemaker-user@default:~$ curl -s --unix-sock /docker/proxy.sock http://localhost:8080/version
Moved Permanently.
```

### Product Category

JupyterLab

### Feedback Category

_No response_

### Other Details

On a fresh SageMaker Studio JupyterLab instance:
```
sagemaker-user@default:~$ docker version
Client:
Version: unknown-version
API version: 1.44 (downgraded from 1.46)
Go version: go1.22.5
Git commit: unknown-commit
Built: unknown-buildtime
OS/Arch: linux/amd64
Context: default

Server:
Engine:
Version: 25.0.6
API version: 1.44 (minimum version 1.24)
Go version: go1.22.5
Git commit: b08a51f
Built: Mon Jul 29 17:22:09 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.7.23
GitCommit: 57f17b0a6295a39009d861b89e3b3b87b005ca27
runc:
Version: 1.1.14
GitCommit: 2c9f5602f0ba3d9da1c2596322dfc4e156844890
docker-init:
Version: 0.19.0
GitCommit: de40ad0
```

```
dillodon ~ % aws sagemaker describe-domain --domain-id $DOMAIN_ID
{
...
"DomainSettings": {
"DockerSettings": {
"EnableDockerAccess": "ENABLED",
"VpcOnlyTrustedAccounts": []
}
},
...
}
```

Full stack trace:
```
2025[/01/24](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/01/24) 22:20:54 INFO mlflow.pyfunc.backend: Building docker image with name mlflow-pyfunc

---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent call last)
File [/opt/conda/lib/python3.11/site-packages/requests/models.py:974](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/requests/models.py#line=973), in Response.json(self, **kwargs)
973 try:
--> 974 return complexjson.loads(self.text, **kwargs)
975 except JSONDecodeError as e:
976 # Catch JSON-related errors and raise as requests.JSONDecodeError
977 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError

File [/opt/conda/lib/python3.11/json/__init__.py:346](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/json/__init__.py#line=345), in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
343 if (cls is None and object_hook is None and
344 parse_int is None and parse_float is None and
345 parse_constant is None and object_pairs_hook is None and not kw):
--> 346 return _default_decoder.decode(s)
347 if cls is None:

File [/opt/conda/lib/python3.11/json/decoder.py:337](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/json/decoder.py#line=336), in JSONDecoder.decode(self, s, _w)
333 """Return the Python representation of ``s`` (a ``str`` instance
334 containing a JSON document).
335
336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
338 end = _w(s, end).end()

File [/opt/conda/lib/python3.11/json/decoder.py:355](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/json/decoder.py#line=354), in JSONDecoder.raw_decode(self, s, idx)
354 except StopIteration as err:
--> 355 raise JSONDecodeError("Expecting value", s, err.value) from None
356 return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

JSONDecodeError Traceback (most recent call last)
File [/opt/conda/lib/python3.11/site-packages/docker/api/client.py:223](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/api/client.py#line=222), in APIClient._retrieve_server_version(self)
222 try:
--> 223 return self.version(api_version=False)["ApiVersion"]
224 except KeyError as ke:

File [/opt/conda/lib/python3.11/site-packages/docker/api/daemon.py:181](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/api/daemon.py#line=180), in DaemonApiMixin.version(self, api_version)
180 url = self._url("[/version](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/version)", versioned_api=api_version)
--> 181 return self._result(self._get(url), json=True)

File [/opt/conda/lib/python3.11/site-packages/docker/api/client.py:284](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/api/client.py#line=283), in APIClient._result(self, response, json, binary)
283 if json:
--> 284 return response.json()
285 if binary:

File [/opt/conda/lib/python3.11/site-packages/requests/models.py:978](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/requests/models.py#line=977), in Response.json(self, **kwargs)
975 except JSONDecodeError as e:
976 # Catch JSON-related errors and raise as requests.JSONDecodeError
977 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 978 raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The above exception was the direct cause of the following exception:

DockerException Traceback (most recent call last)
Cell In[5], line 1
----> 1 mlflow.models.build_docker("models:[/dillodon-ml-pipeline-model-one/1](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/dillodon-ml-pipeline-model-one/1)")

File [/opt/conda/lib/python3.11/site-packages/mlflow/models/python_api.py:79](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/mlflow/models/python_api.py#line=78), in build_docker(model_uri, name, env_manager, mlflow_home, install_java, install_mlflow, enable_mlserver)
17 def build_docker(
18 model_uri=None,
19 name="mlflow-pyfunc",
(...)
24 enable_mlserver=False,
25 ):
26 """
27 Builds a Docker image whose default entrypoint serves an MLflow model at port 8080, using the
28 python_function flavor. The container serves the model referenced by ``model_uri``, if
(...)
77 mlflow_home: Path to local clone of MLflow project. Use for development only.
78 """
---> 79 get_flavor_backend(model_uri, docker_build=True, env_manager=env_manager).build_image(
80 model_uri,
81 name,
82 mlflow_home=mlflow_home,
83 install_java=install_java,
84 install_mlflow=install_mlflow,
85 enable_mlserver=enable_mlserver,
86 )

File [/opt/conda/lib/python3.11/site-packages/mlflow/pyfunc/backend.py:368](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/mlflow/pyfunc/backend.py#line=367), in PyFuncBackend.build_image(self, model_uri, image_name, install_java, install_mlflow, mlflow_home, enable_mlserver)
358 self.generate_dockerfile(
359 model_uri=model_uri,
360 output_dir=cwd,
(...)
364 enable_mlserver=enable_mlserver,
365 )
367 _logger.info("Building docker image with name %s", image_name)
--> 368 docker_utils.build_image_from_context(context_dir=cwd, image_name=image_name)

File [/opt/conda/lib/python3.11/site-packages/mlflow/models/docker_utils.py:214](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/mlflow/models/docker_utils.py#line=213), in build_image_from_context(context_dir, image_name)
211 def build_image_from_context(context_dir: str, image_name: str):
212 import docker
--> 214 client = docker.from_env()
215 # In Docker < 19, `docker build` doesn't support the `--platform` option
216 is_platform_supported = int(client.version()["Version"].split(".")[0]) >= 19

File [/opt/conda/lib/python3.11/site-packages/docker/client.py:94](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/client.py#line=93), in DockerClient.from_env(cls, **kwargs)
92 version = kwargs.pop('version', None)
93 use_ssh_client = kwargs.pop('use_ssh_client', False)
---> 94 return cls(
95 timeout=timeout,
96 max_pool_size=max_pool_size,
97 version=version,
98 use_ssh_client=use_ssh_client,
99 **kwargs_from_env(**kwargs)
100 )

File [/opt/conda/lib/python3.11/site-packages/docker/client.py:45](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/client.py#line=44), in DockerClient.__init__(self, *args, **kwargs)
44 def __init__(self, *args, **kwargs):
---> 45 self.api = APIClient(*args, **kwargs)

File [/opt/conda/lib/python3.11/site-packages/docker/api/client.py:207](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/api/client.py#line=206), in APIClient.__init__(self, base_url, version, timeout, tls, user_agent, num_pools, credstore_env, use_ssh_client, max_pool_size)
202 # version detection needs to be after unix adapter mounting
203 if version is None or (isinstance(
204 version,
205 str
206 ) and version.lower() == 'auto'):
--> 207 self._version = self._retrieve_server_version()
208 else:
209 self._version = version

File [/opt/conda/lib/python3.11/site-packages/docker/api/client.py:230](https://bcwbmftgwyzovsx.studio.us-west-2.sagemaker.aws/opt/conda/lib/python3.11/site-packages/docker/api/client.py#line=229), in APIClient._retrieve_server_version(self)
225 raise DockerException(
226 'Invalid response from docker daemon: key "ApiVersion"'
227 ' is missing.'
228 ) from ke
229 except Exception as e:
--> 230 raise DockerException(
231 f'Error while fetching server API version: {e}'
232 ) from e

DockerException: Error while fetching server API version: Expecting value: line 1 column 1 (char 0)
```

Contributor guide

Open the contributing guide

Research direction

Reproduce the redirect through the SageMaker Studio Docker proxy, then inspect mlflow/models/docker_utils.py and docker/api/daemon.py around the unversioned version request. Compare the bare /version response with /v1.41/version and verify that the Docker client and MLflow image build no longer fail with JSONDecodeError.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, docker, python
Domain
cloud, devops
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.