Compare commits

...

18 Commits

Author SHA1 Message Date
Frederik Ring
ac3a231d2b Merge pull request #7 from offen/swarm-support
Support Docker in Swarm mode
2021-07-08 20:07:06 +02:00
Frederik Ring
054ab8fbe6 when stopped container was part of a stack service, update service instead 2021-07-08 19:54:04 +02:00
Frederik Ring
fa356137e8 inject proper version for mc command at compile time 2021-07-03 10:25:26 +02:00
Frederik Ring
07befda44d build mc from source, support arm/v7 2021-07-01 15:16:39 +02:00
Frederik Ring
c33ebc0c70 Merge pull request #5 from offen/multiarch
Build image for arm architectures
2021-07-01 14:22:54 +02:00
Frederik Ring
23c287bfc7 build image for arm architectures 2021-07-01 14:19:55 +02:00
Frederik Ring
5be3c36040 update alpine base image to 3.14 2021-06-28 20:25:21 +02:00
Frederik Ring
57afad5727 Merge pull request #4 from offen/stop-label
Allow for making container stop filter configurable
2021-06-26 21:19:35 +02:00
Frederik Ring
bafca7bb85 allow for making container stop filter configurable 2021-06-26 21:16:22 +02:00
Frederik Ring
84afc43fd8 Merge pull request #2 from offen/mc-extra-flags
Allow passing custom arguments to minio client
2021-05-25 07:37:00 +02:00
Frederik Ring
1af345061c use global options naming to be in line with minio docs 2021-05-25 07:35:21 +02:00
Frederik Ring
5368eb8c5e allow passing custom arguments to minio client 2021-05-24 20:34:30 +02:00
Frederik Ring
5978a897ad document leeway option when pruning 2021-04-08 17:24:44 +02:00
Frederik Ring
203bad3427 add missing sleep call 2021-04-08 08:27:21 +02:00
Frederik Ring
2892369677 sleep 10 minutes before pruning backups
if pruning happens immediatley after taking the backup a race condition
can occur where a backup that would theoretically be eligible won't be
pruned as the current backup run was very fast, putting the potential
candidate just at the very edge of the selected time window
2021-04-08 08:17:55 +02:00
Frederik Ring
204a0862c6 make log output more consistent 2021-04-03 09:33:11 +02:00
Frederik Ring
17a3523ded tweak comparison in README 2021-04-02 14:44:28 +02:00
Frederik Ring
c5ab795f9c add tag condition for ci 2021-04-02 14:24:48 +02:00
6 changed files with 150 additions and 49 deletions

View File

@@ -1,39 +1,72 @@
version: 2.1
jobs:
build:
canary:
docker:
- image: cimg/base:2020.06
working_directory: ~/docker-volume-backup
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
version: 20.10.6
- run:
name: Build
command: make build
- run:
name: Check if image needs to be pushed
command: |
if [[ -z "$CIRCLE_TAG" ]]; then
echo "Not a git tag, nothing to do ..."
circleci-agent step halt
docker build . -t offen/docker-volume-backup:canary
- run:
name: Create container from image
command: |
docker run -d offen/docker-volume-backup:canary
echo "Sleeping for 30s before checking if container is still running."
sleep 30
count=$(docker ps -q | wc -l)
if [[ $count != "1" ]]; then
echo "Expected one container to be running, found $count."
exit 1
fi
docker stop $(docker ps -q)
build:
docker:
- image: cimg/base:2020.06
environment:
DOCKER_BUILDKIT: '1'
DOCKER_CLI_EXPERIMENTAL: enabled
working_directory: ~/docker-volume-backup
steps:
- checkout
- setup_remote_docker:
version: 20.10.6
- docker/install-docker-credential-helper
- docker/configure-docker-credentials-store
- run:
name: Push to Docker Hub
command: |
echo "$DOCKER_ACCESSTOKEN" | docker login --username offen --password-stdin
docker tag offen/docker-volume-backup:local offen/docker-volume-backup:$CIRCLE_TAG
docker tag offen/docker-volume-backup:local offen/docker-volume-backup:latest
docker push offen/docker-volume-backup:$CIRCLE_TAG
docker push offen/docker-volume-backup:latest
# This is required for building ARM: https://gitlab.alpinelinux.org/alpine/aports/-/issues/12406
docker run --rm --privileged linuxkit/binfmt:v0.8
docker context create docker-volume-backup
docker buildx create docker-volume-backup --name docker-volume-backup --use
docker buildx inspect --bootstrap
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
-t offen/docker-volume-backup:$CIRCLE_TAG \
-t offen/docker-volume-backup:latest \
. --push
workflows:
version: 2
deploy:
docker_image:
jobs:
- build
- canary:
filters:
tags:
ignore: /^v.*/
- build:
filters:
branches:
ignore: /.*/
tags:
only: /^v.*/
orbs:
docker: circleci/docker@1.0.1

View File

@@ -1,16 +1,20 @@
# Copyright 2021 - Offen Authors <hioffen@posteo.de>
# SPDX-License-Identifier: MIT
# SPDX-License-Identifier: MPL-2.0
FROM alpine:3.13
FROM golang:1.16-alpine as builder
ARG MC_VERSION=RELEASE.2021-06-13T17-48-22Z
RUN go install -ldflags "-X github.com/minio/mc/cmd.ReleaseTag=$MC_VERSION" github.com/minio/mc@$MC_VERSION
FROM alpine:3.14
WORKDIR /root
RUN apk add --update ca-certificates docker openrc gnupg
RUN update-ca-certificates
RUN rc-update add docker boot
RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc && \
chmod +x mc && \
mv mc /usr/bin/mc
COPY --from=builder /go/bin/mc /usr/bin/mc
RUN mc --version
COPY src/backup.sh src/entrypoint.sh /root/
RUN chmod +x backup.sh && mv backup.sh /usr/bin/backup \

View File

@@ -1,5 +0,0 @@
DOCKER_TAG ?= local
.PHONY: build
build:
@docker build -t offen/docker-volume-backup:$(DOCKER_TAG) .

View File

@@ -28,7 +28,7 @@ AWS_SECRET_ACCESS_KEY="<xxx>"
AWS_S3_BUCKET_NAME="<xxx>"
# This is the FQDN of your storage server, e.g. `storage.example.com`.
# You can leave it blank when working against AWS S3.
# Do not set this when working against AWS S3.
# AWS_ENDPOINT="<xxx>"
########### BACKUP PRUNING
@@ -38,11 +38,39 @@ AWS_S3_BUCKET_NAME="<xxx>"
# BACKUP_RETENTION_DAYS="7"
# In case the duration a backup takes fluctuates noticeably in your setup
# you can adjust this setting to make sure there are no race conditions
# between the backup finishing and the pruning not deleting backups that
# sit on the very edge of the time window. Set this value to a duration
# that is expected to be bigger than the maximum difference of backups.
# Valid values have a suffix of (s)econds, (m)inutes, (h)ours, or (d)ays.
# BACKUP_PRUNING_LEEWAY="10m"
########### BACKUP ENCRYPTION
# Backups can be encrypted using gpg in case a passphrase is given
# GPG_PASSPHRASE="<xxx>"
########### STOPPING CONTAINERS DURING BACKUP
# Containers can be stopped by applying a
# `docker-volume-backup.stop-during-backup` label. By default, all containers
# that are labeled with `true` will be stopped. If you need more fine grained
# control (e.g. when running multiple containers based on this image), you can
# override this default by specifying a different value here.
# BACKUP_STOP_CONTAINER_LABEL="service1"
########### MINIO CLIENT CONFIGURATION
# Pass these additional flags to all MinIO client `mc` invocations.
# This can be used for example to pass `--insecure` when using self
# signed certificates, or passing `--debug` to gain insights on
# unexpected behavior.
# MC_GLOBAL_OPTIONS="<xxx>"
```
## Example in a docker-compose setup
@@ -78,9 +106,19 @@ volumes:
data:
```
## Using with Docker Swarm
By default, Docker Swarm will restart stopped containers automatically, even when manually stopped. If you plan to have your containers / services stopped during backup, this means you need to apply the `on-failure` restart policy to your service's definitions. A restart policy of `always` is not compatible with this tool.
---
## Differences to `futurice/docker-volume-backup`
This image is heavily inspired by the `futurice/docker-volume-backup`. We decided to publish this image because of the following requirements:
This image is heavily inspired by the `futurice/docker-volume-backup`. We decided to publish this image as a simpler and more lightweight alternative because of the following requirements:
- The original image is based on `ubuntu`, making it very heavy. This version is roughly 500MB smaller in size.
- The original image is based on `ubuntu`, making it very heavy. This version is roughly 1/3 in compressed size.
- This image makes use of the MinIO client `mc` instead of the full blown AWS CLI for uploading backups.
- The original image proposed to handle backup rotation through AWS S3 lifecycle policies. This image adds the option to rotate old backups through the same script so this functionality can also be offered for non-AWS storage backends like MinIO.
- InfluxDB specific functionality was removed.
- `arm64` and `arm/v7` architectures are supported.
- Docker in Swarm mode is supported.

View File

@@ -12,25 +12,25 @@ function info {
echo -e "\n[INFO] $1\n"
}
info "Backup starting"
info "Preparing backup"
DOCKER_SOCK="/var/run/docker.sock"
if [ -S "$DOCKER_SOCK" ]; then
TEMPFILE="$(mktemp)"
docker ps \
--format "{{.ID}}" \
--filter "label=docker-volume-backup.stop-during-backup=true" \
--filter "label=docker-volume-backup.stop-during-backup=$BACKUP_STOP_CONTAINER_LABEL" \
> "$TEMPFILE"
CONTAINERS_TO_STOP="$(cat $TEMPFILE | tr '\n' ' ')"
CONTAINERS_TO_STOP_TOTAL="$(cat $TEMPFILE | wc -l)"
CONTAINERS_TOTAL="$(docker ps --format "{{.ID}}" | wc -l)"
rm "$TEMPFILE"
echo "$CONTAINERS_TOTAL containers running on host in total"
echo "$CONTAINERS_TO_STOP_TOTAL containers marked to be stopped during backup"
echo "$CONTAINERS_TOTAL containers running on host in total."
echo "$CONTAINERS_TO_STOP_TOTAL containers marked to be stopped during backup."
else
CONTAINERS_TO_STOP_TOTAL="0"
CONTAINERS_TOTAL="0"
echo "Cannot access \"$DOCKER_SOCK\", won't look for containers to stop"
echo "Cannot access \"$DOCKER_SOCK\", won't look for containers to stop."
fi
if [ "$CONTAINERS_TO_STOP_TOTAL" != "0" ]; then
@@ -39,7 +39,7 @@ if [ "$CONTAINERS_TO_STOP_TOTAL" != "0" ]; then
fi
info "Creating backup"
BACKUP_FILENAME="$(date +"${BACKUP_FILENAME:-backup-%Y-%m-%dT%H-%M-%S.tar.gz}")"
BACKUP_FILENAME="$(date +"$BACKUP_FILENAME")"
tar -czvf "$BACKUP_FILENAME" $BACKUP_SOURCES # allow the var to expand, in case we have multiple sources
if [ ! -z "$GPG_PASSPHRASE" ]; then
@@ -51,15 +51,38 @@ if [ ! -z "$GPG_PASSPHRASE" ]; then
fi
if [ "$CONTAINERS_TO_STOP_TOTAL" != "0" ]; then
info "Starting containers back up"
docker start $CONTAINERS_TO_STOP
info "Starting containers/services back up"
# The container might be part of a stack when running in swarm mode, so
# its parent service needs to be restarted instead once backup is finished.
SERVICES_REQUIRING_UPDATE=""
for CONTAINER_ID in $CONTAINERS_TO_STOP; do
SWARM_SERVICE_NAME=$(
docker inspect \
--format "{{ index .Config.Labels \"com.docker.swarm.service.name\" }}" \
$CONTAINER_ID
)
if [ -z "$SWARM_SERVICE_NAME" ]; then
echo "Restarting $(docker start $CONTAINER_ID)"
else
echo "Removing $(docker rm $CONTAINER_ID)"
# Multiple containers might belong to the same service, so they will
# be restarted only after all names are known.
SERVICES_REQUIRING_UPDATE="${SERVICES_REQUIRING_UPDATE} ${SWARM_SERVICE_NAME}"
fi
done
if [ -n "$SERVICES_REQUIRING_UPDATE" ]; then
for SERVICE_NAME in "$(echo -n "$SERVICES_REQUIRING_UPDATE" | tr ' ' '\n' | sort -u)"; do
docker service update --force $SERVICE_NAME
done
fi
fi
if [ ! -z "$AWS_S3_BUCKET_NAME" ]; then
info "Uploading backup to remote storage"
echo "Will upload to bucket \"$AWS_S3_BUCKET_NAME\""
mc cp "$BACKUP_FILENAME" "backup-target/$AWS_S3_BUCKET_NAME"
echo "Upload finished"
echo "Will upload to bucket \"$AWS_S3_BUCKET_NAME\"."
mc cp $MC_GLOBAL_OPTIONS "$BACKUP_FILENAME" "backup-target/$AWS_S3_BUCKET_NAME"
echo "Upload finished."
fi
if [ -f "$BACKUP_FILENAME" ]; then
@@ -68,26 +91,29 @@ if [ -f "$BACKUP_FILENAME" ]; then
fi
info "Backup finished"
echo "Will wait for next scheduled backup"
echo "Will wait for next scheduled backup."
if [ ! -z "$BACKUP_RETENTION_DAYS" ]; then
info "Pruning old backups"
echo "Sleeping ${BACKUP_PRUNING_LEEWAY} before checking eligibility."
sleep "$BACKUP_PRUNING_LEEWAY"
bucket=$AWS_S3_BUCKET_NAME
rule_applies_to=$(mc rm --fake --recursive -force --older-than "${BACKUP_RETENTION_DAYS}d" "backup-target/$bucket" | wc -l)
rule_applies_to=$(mc rm $MC_GLOBAL_OPTIONS --fake --recursive -force --older-than "${BACKUP_RETENTION_DAYS}d" "backup-target/$bucket" | wc -l)
if [ "$rule_applies_to" == "0" ]; then
echo "No backups found that match the configured retention period. Doing nothing."
echo "No backups found older than the configured retention period of $BACKUP_RETENTION_DAYS days."
echo "Doing nothing."
exit 0
fi
available=$(mc ls "backup-target/$bucket" | wc -l)
total=$(mc ls $MC_GLOBAL_OPTIONS "backup-target/$bucket" | wc -l)
if [ "$rule_applies_to" == "$available" ]; then
echo "Using a retention of $BACKUP_RETENTION_DAYS days would prune all currently existing backups, will not continue."
if [ "$rule_applies_to" == "$total" ]; then
echo "Using a retention of ${BACKUP_RETENTION_DAYS} days would prune all currently existing backups, will not continue."
echo "If this is what you want, please remove files manually instead of using this script."
exit 1
fi
mc rm --recursive -force --older-than "${BACKUP_RETENTION_DAYS}d" "backup-target/$bucket"
echo "Successfully pruned all backups older than ${BACKUP_RETENTION_DAYS} days"
mc rm $MC_GLOBAL_OPTIONS --recursive -force --older-than "${BACKUP_RETENTION_DAYS}d" "backup-target/$bucket"
echo "Successfully pruned ${rule_applies_to} backups older than ${BACKUP_RETENTION_DAYS} days."
fi

View File

@@ -15,21 +15,26 @@ BACKUP_CRON_EXPRESSION="${BACKUP_CRON_EXPRESSION:-@daily}"
BACKUP_FILENAME=${BACKUP_FILENAME:-"backup-%Y-%m-%dT%H-%M-%S.tar.gz"}
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-}"
BACKUP_PRUNING_LEEWAY="${BACKUP_PRUNING_LEEWAY:-10m}"
AWS_S3_BUCKET_NAME="${AWS_S3_BUCKET_NAME:-}"
AWS_ENDPOINT="${AWS_ENDPOINT:-s3.amazonaws.com}"
GPG_PASSPHRASE="${GPG_PASSPHRASE:-}"
BACKUP_STOP_CONTAINER_LABEL="${BACKUP_STOP_CONTAINER_LABEL:-true}"
MC_GLOBAL_OPTIONS="${MC_GLOBAL_OPTIONS:-}"
EOF
chmod a+x env.sh
source env.sh
mc alias set backup-target "https://$AWS_ENDPOINT" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY"
mc $MC_GLOBAL_OPTIONS alias set backup-target "https://$AWS_ENDPOINT" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY"
# Add our cron entry, and direct stdout & stderr to Docker commands stdout
echo "Installing cron.d entry with expression $BACKUP_CRON_EXPRESSION"
echo "Installing cron.d entry with expression $BACKUP_CRON_EXPRESSION."
echo "$BACKUP_CRON_EXPRESSION backup 2>&1" | crontab -
# Let cron take the wheel
echo "Starting cron in foreground"
echo "Starting cron in foreground."
crond -f -l 8