aws / aws/amazon-sagemaker-feedback
SageMaker Studio Docker support does not support un-versioned Docker APIs
- 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
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