| 1 | #!/bin/bash |
| 2 | |
| 3 | # Copyright (c) 2014 Spotify AB. |
| 4 | # |
| 5 | # Licensed to the Apache Software Foundation (ASF) under one |
| 6 | # or more contributor license agreements. See the NOTICE file |
| 7 | # distributed with this work for additional information |
| 8 | # regarding copyright ownership. The ASF licenses this file |
| 9 | # to you under the Apache License, Version 2.0 (the |
| 10 | # "License"); you may not use this file except in compliance |
| 11 | # with the License. You may obtain a copy of the License at |
| 12 | # |
| 13 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 14 | # |
| 15 | # Unless required by applicable law or agreed to in writing, |
| 16 | # software distributed under the License is distributed on an |
| 17 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 18 | # KIND, either express or implied. See the License for the |
| 19 | # specific language governing permissions and limitations |
| 20 | # under the License. |
| 21 | |
| 22 | # This script attempts to garbage collect docker containers and images. |
| 23 | # Containers that exited more than an hour ago are removed. |
| 24 | # Images that have existed more than an hour and are not in use by any |
| 25 | # containers are removed. |
| 26 | |
| 27 | # Note: Although docker normally prevents removal of images that are in use by |
| 28 | # containers, we take extra care to not remove any image tags (e.g. |
| 29 | # ubuntu:14.04, busybox, etc) that are used by containers. A naive |
| 30 | # "docker rmi `docker images -q`" will leave images stripped of all tags, |
| 31 | # forcing users to re-pull the repositories even though the images |
| 32 | # themselves are still on disk. |
| 33 | |
| 34 | # Note: State is stored in $STATE_DIR, defaulting to /var/lib/docker-gc |
| 35 | |
| 36 | set -o nounset |
| 37 | set -o errexit |
| 38 | |
| 39 | GRACE_PERIOD_SECONDS=${GRACE_PERIOD_SECONDS:=3600} |
| 40 | STATE_DIR=${STATE_DIR:=/var/lib/docker-gc} |
| 41 | DOCKER=${DOCKER:=docker} |
| 42 | EXCLUDE_FROM_GC=${EXCLUDE_FROM_GC:=/etc/docker-gc-exclude} |
| 43 | if [ ! -f "$EXCLUDE_FROM_GC" ] |
| 44 | then |
| 45 | EXCLUDE_FROM_GC=/dev/null |
| 46 | fi |
| 47 | |
| 48 | EXCLUDE_IDS_FILE="exclude_ids" |
| 49 | |
| 50 | function date_parse { |
| 51 | if date --utc >/dev/null 2>&1; then |
| 52 | # GNU/date |
| 53 | echo $(date -u --date "${1}" "+%s") |
| 54 | else |
| 55 | # BSD/date |
| 56 | echo $(date -j -u -f "%F %T" "${1}" "+%s") |
| 57 | fi |
| 58 | } |
| 59 | |
| 60 | |
| 61 | # Elapsed time since a docker timestamp, in seconds |
| 62 | function elapsed_time() { |
| 63 | # Docker 1.5.0 datetime format is 2015-07-03T02:39:00.390284991 |
| 64 | # Docker 1.7.0 datetime format is 2015-07-03 02:39:00.390284991 +0000 UTC |
| 65 | utcnow=$(date -u "+%s") |
| 66 | without_ms="${1:0:19}" |
| 67 | replace_t="${without_ms/T/ }" |
| 68 | epoch=$(date_parse "${replace_t}") |
| 69 | echo $(($utcnow - $epoch)) |
| 70 | } |
| 71 | |
| 72 | function compute_exclude_ids() { |
| 73 | # Find images that match patterns in the EXCLUDE_FROM_GC file and put their |
| 74 | # id prefixes into $EXCLUDE_IDS_FILE, prefixed with ^ |
| 75 | |
| 76 | PROCESSED_EXCLUDES="processed_excludes.tmp" |
| 77 | # Take each line and put a space at the beginning and end, so when we |
| 78 | # grep for them below, it will effectively be: "match either repo:tag |
| 79 | # or imageid". Also delete blank lines or lines that only contain |
| 80 | # whitespace |
| 81 | sed 's/^\(.*\)$/ \1 /' $EXCLUDE_FROM_GC | sed '/^ *$/d' > $PROCESSED_EXCLUDES |
| 82 | # The following looks a bit of a mess, but here's what it does: |
| 83 | # 1. Get images |
| 84 | # 2. Skip header line |
| 85 | # 3. Turn columnar display of 'REPO TAG IMAGEID ....' to 'REPO:TAG IMAGEID' |
| 86 | # 4. find lines that contain things mentioned in PROCESSED_EXCLUDES |
| 87 | # 5. Grab the image id from the line |
| 88 | # 6. Prepend ^ to the beginning of each line |
| 89 | |
| 90 | # What this does is make grep patterns to match image ids mentioned by |
| 91 | # either repo:tag or image id for later greppage |
| 92 | $DOCKER images \ |
| 93 | | tail -n+2 \ |
| 94 | | sed 's/^\([^ ]*\) *\([^ ]*\) *\([^ ]*\).*/ \1:\2 \3 /' \ |
| 95 | | grep -f $PROCESSED_EXCLUDES 2>/dev/null \ |
| 96 | | cut -d' ' -f3 \ |
| 97 | | sed 's/^/^/' > $EXCLUDE_IDS_FILE |
| 98 | } |
| 99 | |
| 100 | # Change into the state directory (and create it if it doesn't exist) |
| 101 | if [ ! -d "$STATE_DIR" ] |
| 102 | then |
| 103 | mkdir -p $STATE_DIR |
| 104 | fi |
| 105 | cd "$STATE_DIR" |
| 106 | |
| 107 | # Verify that docker is reachable |
| 108 | $DOCKER version 1>/dev/null |
| 109 | |
| 110 | # List all currently existing containers |
| 111 | $DOCKER ps -a -q --no-trunc | sort | uniq > containers.all |
| 112 | |
| 113 | # List running containers |
| 114 | $DOCKER ps -q --no-trunc | sort | uniq > containers.running |
| 115 | |
| 116 | # compute ids of containers to exclude from GC |
| 117 | compute_exclude_ids |
| 118 | |
| 119 | # List containers that are not running |
| 120 | comm -23 containers.all containers.running > containers.exited |
| 121 | |
| 122 | # Find exited containers that finished at least GRACE_PERIOD_SECONDS ago |
| 123 | echo -n "" > containers.reap.tmp |
| 124 | cat containers.exited | while read line |
| 125 | do |
| 126 | EXITED=$(${DOCKER} inspect -f "{{json .State.FinishedAt}}" ${line}) |
| 127 | ELAPSED=$(elapsed_time $EXITED) |
| 128 | if [[ $ELAPSED -gt $GRACE_PERIOD_SECONDS ]]; then |
| 129 | echo $line >> containers.reap.tmp |
| 130 | fi |
| 131 | done |
| 132 | cat containers.reap.tmp | sort | uniq > containers.reap |
| 133 | |
| 134 | # List containers that we will keep. |
| 135 | comm -23 containers.all containers.reap > containers.keep |
| 136 | |
| 137 | # List images used by containers that we keep. |
| 138 | # This may be both image id's and repo/name:tag, so normalize to image id's only |
| 139 | cat containers.keep | |
| 140 | xargs -n 1 $DOCKER inspect -f '{{.Config.Image}}' 2>/dev/null | |
| 141 | sort | uniq | |
| 142 | xargs -n 1 $DOCKER inspect -f '{{.Id}}' 2>/dev/null | |
| 143 | sort | uniq > images.used |
| 144 | |
| 145 | # List images to reap; images that existed last run and are not in use. |
| 146 | $DOCKER images -q --no-trunc | sort | uniq > images.all |
| 147 | comm -23 images.all images.used | grep -v -f $EXCLUDE_IDS_FILE > images.reap || true |
| 148 | |
| 149 | # Reap containers. |
| 150 | xargs -n 1 $DOCKER rm --volumes=true < containers.reap &>/dev/null || true |
| 151 | |
| 152 | # Reap images. |
| 153 | xargs -n 1 $DOCKER rmi < images.reap &>/dev/null || true |