Merge pull request #2983 from yueguobin/fix/docker-image-digest-translation

fix(docker): accept the pinned image id when the two daemons report different ids for the same content
This commit is contained in:
Jeremy Grossmann authored and GitHub committed 2026-10-13 16:23:39 +02:00
commit b08441b9ad
8 files changed
+259 -19

No files matched your search

+44 -1
View File
@@ -66,6 +66,11 @@ class Docker(BaseManager):
self._session = None
self._api_version = DOCKER_MINIMUM_API_VERSION
self._host_checked = False
# Image name -> (controller digest, local id) for the images synced from
# the controller. The controller pins the id its own daemon reports,
# which is not comparable with the id this daemon reports for the same
# content (see load_image()).
self._image_id_translations = {}
def _select_node_class(self, **kwargs):
"""
@@ -419,11 +424,16 @@ class Docker(BaseManager):
progress_callback(f"Success pulling image {image}")
@locking
async def load_image(self, stream, progress_callback=None):
async def load_image(self, stream, image=None, digest=None, progress_callback=None):
"""
Load a Docker image into the Docker daemon from a docker save tar stream
:param stream: An async iterable of bytes (the tar produced by docker save)
:param image: Tag of the image in the archive. Together with digest, the
(controller digest, local id) pair is remembered so that node creation
can accept the digest the controller pinned for this image
:param digest: Image id as reported by the Docker daemon on the controller
host (the value the controller pins in the node create payload)
:param progress_callback: A function that receive a log message about image load progress
"""
@@ -470,6 +480,39 @@ class Docker(BaseManager):
if progress_callback:
progress_callback("Docker image loaded")
if image and digest:
await self._remember_image_id_translation(image, digest)
async def _remember_image_id_translation(self, image, controller_digest):
"""
Remember how the local daemon identifies an image that was just loaded
from the controller.
Docker ids are not comparable across image stores: for the same content
a classic store reports the config digest, while a containerd store (or
an image loaded from an OCI archive) reports the manifest digest. The
controller pins the id of its own daemon, so recording the pair lets
DockerVM accept that pin for the image it actually received instead of
asking for another full sync on every node creation.
"""
try:
image_infos = await self.query("GET", f"images/{image}/json")
except DockerError as e:
log.warning(f"Image '{image}' has been loaded but cannot be inspected: {e}")
return
local_id = image_infos.get("Id")
if local_id:
self._image_id_translations[image] = (controller_digest, local_id)
def image_id_translation(self, image):
"""
:returns: the (controller digest, local id) pair recorded the last time
this image was loaded from the controller, None otherwise
"""
return self._image_id_translations.get(image)
async def list_images(self):
"""
Gets Docker image list.
+13 -8
View File
@@ -591,15 +591,20 @@ class DockerVM(DockerKernelDatapathMixin, BaseNode):
if image_infos is None:
raise DockerError(f"Cannot get information for image '{self._image}', please try again.")
if self._image_digest and image_infos.get("Id") != self._image_digest:
local_id = image_infos.get("Id")
if self._image_digest and local_id != self._image_digest:
# the tag exists but points to a different image (e.g. a moved :latest):
# report it as missing so the controller re-syncs the expected version
local_id = image_infos.get("Id")
log.info(
f"Image '{self._image}' version mismatch on this compute: "
f"local id '{local_id}' != expected '{self._image_digest}'"
)
raise ImageMissingError(self._image)
# report it as missing so the controller re-syncs the expected version.
# Image ids are not comparable across image stores (a classic store
# reports the config digest where a containerd store, or an image
# loaded from an OCI archive, reports the manifest digest), so accept
# the pin for the image this compute received from the controller.
if self.manager.image_id_translation(self._image) != (self._image_digest, local_id):
log.info(
f"Image '{self._image}' version mismatch on this compute: "
f"local id '{local_id}' != expected '{self._image_digest}'"
)
raise ImageMissingError(self._image)
available_cpus = psutil.cpu_count(logical=True)
if self._cpus > available_cpus: