From a5b469f612864454f6eb91e6ca212828bac5e42b Mon Sep 17 00:00:00 2001 From: Adar Nimrod <nimrod@shore.co.il> Date: Sun, 31 Oct 2021 15:26:06 +0200 Subject: [PATCH] Recreate the backup script with shell, AWK, reg and skopeo. A few reasons. First of all, it doesn't require a running Docker daemon instead outputting directly to a file (faster and saves space). Also, the restore script will probably use skopeo so this the codebase is more uniform. Without the Docker daemon it can run with lower privileges. Lastly, it should work without setting the really high timeout that bugs me a little. --- .gitlab-ci.yml | 5 +- backup/.dockerignore | 1 + backup/Dockerfile | 8 ++- backup/backup | 123 +++++++++++++++++-------------------------- backup/restore | 1 + 5 files changed, 60 insertions(+), 78 deletions(-) create mode 100755 backup/restore diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3b9ff39..e8d010b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,11 +43,10 @@ backup: - >- docker run --volume /var/backups/registry:/var/backups/registry - --volume /run/docker.sock:/run/docker.sock + --user nobody + --rm registry.shore.co.il/registry-backup backup registry.shore.co.il /var/backups/registry - after_script: - - docker image prune -f retry: max: 2 timeout: 3h diff --git a/backup/.dockerignore b/backup/.dockerignore index ff99b53..5664021 100644 --- a/backup/.dockerignore +++ b/backup/.dockerignore @@ -1,2 +1,3 @@ * !backup +!restore diff --git a/backup/Dockerfile b/backup/Dockerfile index 7be4b23..fc73888 100644 --- a/backup/Dockerfile +++ b/backup/Dockerfile @@ -1,4 +1,10 @@ FROM docker.io/library/alpine:3.14 # hadolint ignore=DL3018 -RUN apk add --update --no-cache docker-py +RUN echo 'https://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories && \ + echo 'https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \ + apk add --update --no-cache \ + skopeo \ + reg \ + ; COPY --chown=root:root backup /usr/local/bin/backup +COPY --chown=root:root restore /usr/local/bin/restore diff --git a/backup/backup b/backup/backup index 83c0ea7..fb889fa 100755 --- a/backup/backup +++ b/backup/backup @@ -1,74 +1,49 @@ -#!/usr/bin/env python3 -"""Backup a container image registry.""" - - -import argparse -import pathlib -import sys -import docker -import requests - - -def get_images(registry): - """Return a list of images from the registry.""" - return requests.get(f"https://{registry}/v2/_catalog").json()[ - "repositories" - ] - - -def get_image_tags(registry, image): - """Return a list of tags for an image in the registry.""" - return requests.get(f"https://{registry}/v2/{image}/tags/list").json()[ - "tags" - ] - - -def backup(docker_client, registry, image, tag, dest): - """Backup an image tag from the registry to a file in the destination - directory.""" - full_name = f"{registry}/{image}:{tag}" - print(f"Backing up {full_name}.", file=sys.stderr) - try: - docker_client.images.get(full_name) - image_existed = True - except docker.errors.ImageNotFound: - image_existed = False - docker_image = docker_client.images.pull(full_name) - with open(dest / f"{tag}.tar", "wb") as tarball: - for chunk in docker_image.save(): - tarball.write(chunk) - if not image_existed: - docker_client.images.remove(full_name) - - -def backup_registry(registry, dest): - """Backup the images in the registry to the destination.""" - docker_client = docker.from_env(timeout=600) - docker_client.ping() - for image in get_images(registry): - (dest / image).mkdir(exist_ok=True) - for tag in get_image_tags(registry, image): - backup(docker_client, registry, image, tag, dest / image) - - -if __name__ == "__main__": - arg_parser = argparse.ArgumentParser(description=__doc__) - arg_parser.add_argument("registry", help="FQDN of the registry to backup.") - arg_parser.add_argument( - "destination", help="Location to store the images.", type=pathlib.Path - ) - args = arg_parser.parse_args() - destination = args.destination.expanduser() - if not destination.exists() or not destination.is_dir(): - arg_parser.error( - "Backup destination doesn't exists or isn't a directory." - ) - try: - destination.touch() - except IsADirectoryError: - arg_parser.error("Can't write to the backup destination.") - try: - backup_registry(args.registry, destination) - except Exception as exception: - arg_parser.error(str(exception)) - sys.exit() +#!/bin/sh +set -eu + +usage() { + echo "$0: REGISTRY_DOMAIN BACKUP_DEST" +} + +if [ "${1:-}" = -h ] || [ "${1:-}" = --help ] +then + usage + exit 0 +fi + +if [ "$#" -ne 2 ] +then + usage + exit 1 +fi + +registry="$1" +dest="$2" + +mkdir -p "$dest" + +reg ls "$registry" | \ + sed 's/,//g' | \ + awk -v "registry=$registry" -v "dest=$dest" ' +BEGIN { + exitcode = 0 +} +NR>2 { + system("mkdir -p " dest "/" $1) + for (i=2; i<=NF; i++) { + image_url = registry "/" $1 ":" $(i) + image_file = dest "/" $1 "/" $(i) ".tar" + printf "Saving %s to %s.\n", image_url, image_file + system("rm " image_file) + if (system("skopeo copy docker://" image_url " docker-archive://" image_file) == 0) + printf "Backup of %s was successful.\n", image_url + else { + exitcode = 1 + printf "Backup of %s failed, continuing with other images.\n", image_url + } + } +} +END { + exit retruncode +} +' diff --git a/backup/restore b/backup/restore new file mode 100755 index 0000000..1a24852 --- /dev/null +++ b/backup/restore @@ -0,0 +1 @@ +#!/bin/sh -- GitLab