container: optimize startup of preexisting containers

This commit adds metadata to track loaded OCI archives to allow skipping
'delete + load' of OCI images when restarting either the container or the
system as a whole.  The sha256 of all loaded OCI archives is stored in a
sidecar file in our downloads directory.  Then we verify the checksum of
the OCI archives against their same-named sidecar to determine if the OCI
archive is already loaded or not.

Additionally, the instance using the image is labled with metadata to detect
changes in the container configuration.  This in turn allow skipping the
delete + create phase also of the instance.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-10-23 15:23:52 +02:00
parent c6f3e75aa4
commit 68bb01545c
2 changed files with 143 additions and 3 deletions
+98
View File
@@ -230,6 +230,18 @@ load_archive()
err 1 "failed tagging image as $tag"
fi
# Save archive SHA256 to sidecar file for local archives to enable optimization
if [ -f "$file" ]; then
img_sha256=$(sha256sum "$file" | cut -f1 -d' ')
archive_basename=$(basename "$file")
sha_file="$DOWNLOADS/${archive_basename}.sha256"
mkdir -p "$DOWNLOADS"
echo "$img_sha256" > "$sha_file"
log "Saved archive checksum to $sha_file"
fi
# Clean up after ourselves
if [ -n "$extracted" ]; then
log "Cleaning up extracted $dir"
@@ -295,6 +307,20 @@ create()
args="$args --network=none"
fi
# Add optimization labels for meta-sha256 and config checksum
script="/run/containers/${name}.sh"
if [ -f "$script" ]; then
# Extract meta-sha256 from script if present
meta_sha=$(awk '/^# meta-sha256:/ {print $3}' "$script" 2>/dev/null)
if [ -n "$meta_sha" ]; then
args="$args --label meta-sha256=$meta_sha"
fi
# Add config checksum label
config_sha=$(sha256sum "$script" | cut -f1 -d' ')
args="$args --label config-sha256=$config_sha"
fi
# shellcheck disable=SC2048
log "podman create --name $name --conmon-pidfile=$pidfile $args $image $*"
if podman create --name "$name" --conmon-pidfile="$pidfile" $args "$image" $*; then
@@ -785,6 +811,78 @@ case $cmd in
script=/run/containers/${name}.sh
[ -x "$script" ] || err 1 "setup: $script does not exist or is not executable."
# Early exit optimization: check if container is already up-to-date
meta_sha=$(awk '/^# meta-sha256:/ {print $3}' "$script" 2>/dev/null)
if [ -n "$meta_sha" ]; then
# Local image optimization: check if archive SHA matches stored sidecar file
# Extract the image path from the script
img=$(awk '/^# meta-image:/ {print $3}' "$script" 2>/dev/null)
if [ -n "$img" ]; then
# Determine the archive file path
case "$img" in
oci-archive:*)
archive_path="${img#oci-archive:}"
;;
*)
archive_path="$img"
;;
esac
# Handle relative paths - check BUILTIN and DOWNLOADS
if [ ! -e "$archive_path" ]; then
if [ -e "$DOWNLOADS/$(basename "$archive_path")" ]; then
archive_path="$DOWNLOADS/$(basename "$archive_path")"
elif [ -e "$BUILTIN/$(basename "$archive_path")" ]; then
archive_path="$BUILTIN/$(basename "$archive_path")"
fi
fi
# Check if the archive exists and compare SHA with sidecar file
if [ -f "$archive_path" ]; then
archive_basename=$(basename "$archive_path")
sha_file="$DOWNLOADS/${archive_basename}.sha256"
# Check if sidecar file exists
if [ -f "$sha_file" ]; then
stored_sha=$(cat "$sha_file" 2>/dev/null)
current_sha=$(sha256sum "$archive_path" | cut -f1 -d' ')
# If SHA matches, check container instance
if [ "$stored_sha" = "$current_sha" ]; then
if podman container exists "$name"; then
# Check if container has matching labels
config_sha=$(sha256sum "$script" | cut -f1 -d' ')
container_meta=$(podman inspect "$name" --format '{{index .Config.Labels "meta-sha256"}}' 2>/dev/null)
container_config=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null)
if [ "$container_meta" = "$meta_sha" ] && [ "$container_config" = "$config_sha" ]; then
log "Container $name is up-to-date (archive and config unchanged), skipping setup"
exit 0
fi
fi
else
# Archive changed (e.g., rootfs upgrade) - need to reload
log "Archive SHA changed, will reload image and recreate container"
fi
fi
fi
fi
else
# Remote image optimization: check config-sha256 only
if podman container exists "$name"; then
# Get current config checksum from script
config_sha=$(sha256sum "$script" | cut -f1 -d' ')
# Get container's stored config checksum
container_config=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null)
if [ -n "$container_config" ] && [ "$container_config" = "$config_sha" ]; then
log "Container $name config unchanged, skipping setup"
exit 0
fi
fi
fi
# Save our PID in case we get stuck here and someone wants to
# stop us, e.g., due to reconfiguration or reboot.
pidfile=$(pidfn "${name}")
+45 -3
View File
@@ -22,6 +22,30 @@
#define _PATH_CONT "/run/containers"
/*
* Check if image is a local archive and return the offset to the file path.
* Returns 0 if not a recognized local archive format.
*/
static int archive_offset(const char *image)
{
static const struct {
const char *prefix;
int offset;
} prefixes[] = {
{ "docker-archive:", 15 },
{ "oci-archive:", 12 },
{ NULL, 0 }
};
int i;
for (i = 0; prefixes[i].prefix; i++) {
if (!strncmp(image, prefixes[i].prefix, prefixes[i].offset))
return prefixes[i].offset;
}
return 0;
}
/*
* Create a setup/create/upgrade script and instantiate a new instance
* that Finit will start when all networking and other dependencies are
@@ -37,6 +61,7 @@ static int add(const char *name, struct lyd_node *cif)
struct lyd_node *node, *nets, *caps;
char script[strlen(name) + 5];
FILE *fp, *ap;
int offset;
snprintf(script, sizeof(script), "%s.sh", name);
fp = fopenf("w", "%s/%s", _PATH_CONT, script);
@@ -58,9 +83,26 @@ static int add(const char *name, struct lyd_node *cif)
image = lydx_get_cattr(cif, "image");
fprintf(fp, "#!/bin/sh\n"
"# meta-name: %s\n"
"# meta-image: %s\n"
"container --quiet delete %s >/dev/null\n"
"container --quiet", name, image, name);
"# meta-image: %s\n", name, image);
offset = archive_offset(image);
if (offset) {
const char *path = image + offset;
char sha256[65] = { 0 };
FILE *pp;
pp = popenf("r", "sha256sum %s | cut -f1 -d' '", path);
if (pp) {
if (fgets(sha256, sizeof(sha256), pp)) {
chomp(sha256);
fprintf(fp, "# meta-sha256: %s\n", sha256);
}
pclose(pp);
}
}
fprintf(fp, "container --quiet delete %s >/dev/null\n"
"container --quiet", name);
LYX_LIST_FOR_EACH(lyd_child(cif), node, "dns")
fprintf(fp, " --dns %s", lyd_get_value(node));