diff --git a/dockerfiles/dockerize-ersiliapack/base/Dockerfile.conda b/dockerfiles/dockerize-ersiliapack/base/Dockerfile.conda index 3adf446b..82fc09db 100644 --- a/dockerfiles/dockerize-ersiliapack/base/Dockerfile.conda +++ b/dockerfiles/dockerize-ersiliapack/base/Dockerfile.conda @@ -3,6 +3,7 @@ ARG VERSION=version ENV VERSION=$VERSION ENV PATH=$PATH:/usr/bin/conda/bin WORKDIR /root +COPY . /ersilia-pack RUN set -x && \ apt-get update && \ apt-get install -y wget && \ @@ -27,16 +28,16 @@ RUN set -x && \ echo "Unsupported architecture: $ARCH"; \ echo "$ARCH" > arch.sh; \ fi && \ - apt-get install -y libxrender1 build-essential git && \ mkdir -p /usr/bin/conda && \ bash /root/miniconda.sh -b -u -p /usr/bin/conda && \ rm /root/miniconda.sh && \ . /usr/bin/conda/etc/profile.d/conda.sh && \ conda init && \ /usr/bin/conda/bin/conda clean -afy && \ - /usr/bin/conda/bin/python -m pip install git+https://github.com/ersilia-os/ersilia-pack.git - -COPY docker-entrypoint.sh /root/docker-entrypoint.sh -RUN chmod + /root/docker-entrypoint.sh + cd /ersilia-pack && \ + /usr/bin/conda/bin/python -m pip install . && \ + mv /ersilia-pack/docker-entrypoint.sh /root/docker-entrypoint.sh && \ + chmod +x /root/docker-entrypoint.sh + EXPOSE 80 ENTRYPOINT [ "sh", "/root/docker-entrypoint.sh"] \ No newline at end of file diff --git a/dockerfiles/dockerize-ersiliapack/base/Dockerfile.pip b/dockerfiles/dockerize-ersiliapack/base/Dockerfile.pip index 894dac46..8430e0b6 100644 --- a/dockerfiles/dockerize-ersiliapack/base/Dockerfile.pip +++ b/dockerfiles/dockerize-ersiliapack/base/Dockerfile.pip @@ -1,8 +1,9 @@ FROM python:version -RUN apt-get update && \ - apt-get install git -y && \ - python -m pip install git+https://github.com/ersilia-os/ersilia-pack.git -COPY docker-entrypoint.sh /root/docker-entrypoint.sh -RUN chmod + /root/docker-entrypoint.sh +WORKDIR /root +COPY . /ersilia-pack +RUN apt-get clean && apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* && cd /ersilia-pack && pip install -e . && \ + mv /ersilia-pack/docker-entrypoint.sh /root/docker-entrypoint.sh && \ + chmod + /root/docker-entrypoint.sh EXPOSE 80 ENTRYPOINT [ "sh", "/root/docker-entrypoint.sh"] \ No newline at end of file diff --git a/dockerfiles/dockerize-ersiliapack/base/generate_dockerfiles.py b/dockerfiles/dockerize-ersiliapack/base/generate_dockerfiles.py index 534a36ae..fc2b133e 100644 --- a/dockerfiles/dockerize-ersiliapack/base/generate_dockerfiles.py +++ b/dockerfiles/dockerize-ersiliapack/base/generate_dockerfiles.py @@ -16,15 +16,15 @@ "3.12-slim-bullseye" ] -DOCKER_ENTRYPOINT = """ -#!/bin/bash +# We serve the model at port 80 bec Ersilia port maps all containers to port 80 +DOCKER_ENTRYPOINT = """#!/bin/bash set -ex if [ -z "${MODEL}" ]; then echo "Model name has not been specified" exit 1 fi -ersilia_model_serve --bundle_path /root/bundles/$MODEL --port 3000 +ersilia_model_serve --bundle_path /root/bundles/$MODEL --port 80 echo "Serving model $MODEL..." """ diff --git a/dockerfiles/dockerize-ersiliapack/local.md b/dockerfiles/dockerize-ersiliapack/local.md index 1f215897..2dca4925 100644 --- a/dockerfiles/dockerize-ersiliapack/local.md +++ b/dockerfiles/dockerize-ersiliapack/local.md @@ -22,7 +22,9 @@ Ersilia Pack dockerization is divided into two steps - building the base image t And an entrypoint script called `docker-entrypoint.sh`. -4. Build the base docker image, eg for Python 3.12 conda image, as follows: +4. Copy the desired Dockerfile and `docker-entrypoint.sh` file into ersilia-pack directory. + +5. This step assumes you are inside the ersilia-pack directory and you have completed Step 4. Build the base docker image, eg for Python 3.12 conda image, as follows: ``` docker build -f Dockerfile3.12-slim-bullseye -t ersiliaos/ersiliapack-py312:latest . diff --git a/dockerfiles/dockerize-ersiliapack/model/Dockerfile.conda b/dockerfiles/dockerize-ersiliapack/model/Dockerfile.conda index 8bb01c6d..d9962b81 100644 --- a/dockerfiles/dockerize-ersiliapack/model/Dockerfile.conda +++ b/dockerfiles/dockerize-ersiliapack/model/Dockerfile.conda @@ -9,7 +9,7 @@ RUN conda install -c conda-forge conda-pack RUN conda-pack -n baseclone -o /tmp/env.tar && \ mkdir /venv && cd /venv && tar -xf /tmp/env.tar && \ rm /tmp/env.tar - RUN /venv/bin/conda-unpack +RUN /venv/bin/conda-unpack @@ -26,9 +26,12 @@ ENV PATH="/usr/bin/conda/bin/:$PATH" COPY --from=build /root/bundles /root/bundles -COPY --from=build /root/docker-entrypoint.sh docker-entrypoint.sh +COPY --from=build /root/docker-entrypoint.sh /root/docker-entrypoint.sh COPY --from=build /venv /usr/bin/conda -RUN chmod + docker-entrypoint.sh +RUN cp /root/bundles/$MODEL/*/information.json /root/information.json && \ + cp /root/bundles/$MODEL/*/api_schema.json /root/api_schema.json && \ + cp /root/bundles/$MODEL/*/status.json /root/status.json && \ + chmod + docker-entrypoint.sh EXPOSE 80 ENTRYPOINT [ "sh", "docker-entrypoint.sh"] \ No newline at end of file diff --git a/dockerfiles/dockerize-ersiliapack/model/Dockerfile.pip b/dockerfiles/dockerize-ersiliapack/model/Dockerfile.pip index 18803d6b..f3650ad5 100644 --- a/dockerfiles/dockerize-ersiliapack/model/Dockerfile.pip +++ b/dockerfiles/dockerize-ersiliapack/model/Dockerfile.pip @@ -1,7 +1,10 @@ -FROM ersiliapack-VERSION:latest +FROM ersiliaos/ersiliapack-VERSION:latest ARG MODEL=eos_identifier ENV MODEL=$MODEL WORKDIR /root COPY ./$MODEL /root/$MODEL RUN mkdir /root/bundles && ersilia_model_pack --repo_path $MODEL --bundles_repo_path /root/bundles && \ - rm -rf /root/$MODEL && rm -rf /root/.cache \ No newline at end of file + rm -rf /root/$MODEL && rm -rf /root/.cache && \ + cp /root/bundles/$MODEL/*/information.json /root/information.json && \ + cp /root/bundles/$MODEL/*/api_schema.json /root/api_schema.json && \ + cp /root/bundles/$MODEL/*/status.json /root/status.json \ No newline at end of file diff --git a/ersilia/hub/fetch/lazy_fetchers/dockerhub.py b/ersilia/hub/fetch/lazy_fetchers/dockerhub.py index b3f98fa9..0ac87726 100644 --- a/ersilia/hub/fetch/lazy_fetchers/dockerhub.py +++ b/ersilia/hub/fetch/lazy_fetchers/dockerhub.py @@ -15,7 +15,7 @@ from ...pull.pull import ModelPuller from ....serve.services import PulledDockerImageService from ....setup.requirements.docker import DockerRequirement -from ....utils.docker import SimpleDocker +from ....utils.docker import SimpleDocker, resolve_pack_method_docker, PACK_METHOD_BENTOML from ....utils.exceptions_utils.fetch_exceptions import DockerNotActiveError from .. import STATUS_FILE @@ -50,9 +50,9 @@ def write_apis(self, model_id): di.serve() di.close() - def copy_information(self, model_id): - fr_file = "/root/eos/dest/{0}/{1}".format(model_id, INFORMATION_FILE) - to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, INFORMATION_FILE) + def _copy_from_bentoml_image(self, model_id, file): + fr_file = "/root/eos/dest/{0}/{1}".format(model_id, file) + to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, file) self.simple_docker.cp_from_image( img_path=fr_file, local_path=to_file, @@ -61,9 +61,9 @@ def copy_information(self, model_id): tag=DOCKERHUB_LATEST_TAG, ) - def copy_metadata(self, model_id): - fr_file = "/root/eos/dest/{0}/{1}".format(model_id, API_SCHEMA_FILE) - to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, API_SCHEMA_FILE) + def _copy_from_ersiliapack_image(self, model_id, file): + fr_file = "/root/{0}".format(file) + to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, file) self.simple_docker.cp_from_image( img_path=fr_file, local_path=to_file, @@ -72,18 +72,27 @@ def copy_metadata(self, model_id): tag=DOCKERHUB_LATEST_TAG, ) - def copy_status(self, model_id): - fr_file = "/root/eos/dest/{0}/{1}".format(model_id, STATUS_FILE) - to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, STATUS_FILE) - self.simple_docker.cp_from_image( - img_path=fr_file, - local_path=to_file, - org=DOCKERHUB_ORG, - img=model_id, - tag=DOCKERHUB_LATEST_TAG, - ) + def _copy_from_image_to_local(self, model_id, file): + pack_method = resolve_pack_method_docker(model_id) + if pack_method == PACK_METHOD_BENTOML: + self._copy_from_bentoml_image(model_id, file) + else: + self._copy_from_ersiliapack_image(model_id, file) + + def copy_information(self, model_id): + self.logger.debug("Copying information file from model container") + self._copy_from_image_to_local(model_id, INFORMATION_FILE) + def copy_metadata(self, model_id): + self.logger.debug("Copying api_schema_file file from model container") + self._copy_from_image_to_local(model_id, API_SCHEMA_FILE) + + def copy_status(self, model_id): + self.logger.debug("Copying status file from model container") + self._copy_from_image_to_local(model_id, STATUS_FILE) + def copy_example_if_available(self, model_id): + # TODO This also needs to change to accomodate ersilia pack for pf in PREDEFINED_EXAMPLE_FILES: fr_file = "/root/eos/dest/{0}/{1}".format(model_id, pf) to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, "input.csv") diff --git a/ersilia/utils/docker.py b/ersilia/utils/docker.py index db60cb74..2ec5561a 100644 --- a/ersilia/utils/docker.py +++ b/ersilia/utils/docker.py @@ -10,10 +10,24 @@ from .terminal import run_command, run_command_check_output from .. import logger -from ..default import DEFAULT_DOCKER_PLATFORM, DEFAULT_UDOCKER_USERNAME +from ..default import (DEFAULT_DOCKER_PLATFORM, DEFAULT_UDOCKER_USERNAME, + DOCKERHUB_ORG, DOCKERHUB_LATEST_TAG, + PACK_METHOD_BENTOML, PACK_METHOD_FASTAPI) from ..utils.system import SystemChecker from ..utils.logging import make_temp_dir +def resolve_pack_method_docker(model_id): + client = docker.from_env() + model_image = client.images.get( + f"{DOCKERHUB_ORG}/{model_id}:{DOCKERHUB_LATEST_TAG}" + ) + image_history = model_image.history() + for hist in image_history: + # Very hacky, but works bec we don't have nginx in ersilia-pack images + if "nginx" in hist["CreatedBy"]: + return PACK_METHOD_BENTOML + return PACK_METHOD_FASTAPI + def resolve_platform(): if SystemChecker().is_arm64(): diff --git a/ersilia/utils/paths.py b/ersilia/utils/paths.py index 75ed364b..6112a6ee 100644 --- a/ersilia/utils/paths.py +++ b/ersilia/utils/paths.py @@ -3,6 +3,7 @@ import collections from pathlib import Path from ersilia import logger +from .docker import resolve_pack_method_docker from ..default import PACK_METHOD_BENTOML, PACK_METHOD_FASTAPI MODELS_DEVEL_DIRNAME = "models" @@ -58,11 +59,19 @@ def exists(path): else: return False - -def resolve_pack_method(model_path): +def resolve_pack_method_source(model_path): if os.path.exists(os.path.join(model_path, "installs", "install.sh")): return PACK_METHOD_FASTAPI elif os.path.exists(os.path.join(model_path, "bentoml.yml")): return PACK_METHOD_BENTOML logger.warning("Could not resolve pack method") return None + +def resolve_pack_method(model_path): + with open(os.path.join(model_path, "service_class.txt"), "r") as f: + service_class = f.read().strip() + if service_class == "pulled_docker": + model_id = Paths().model_id_from_path(model_path) + return resolve_pack_method_docker(model_id) + else: + return resolve_pack_method_source(model_path) \ No newline at end of file