You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
7.1 KiB

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