Browse code

API: add "prune" events

This patch adds a new "prune" event type to indicate that pruning of a resource
type completed.

This event-type can be used on systems that want to perform actions after
resources have been cleaned up. For example, Docker Desktop performs an fstrim
after resources are deleted (https://github.com/linuxkit/linuxkit/tree/v0.7/pkg/trim-after-delete).

While the current (remove, destroy) events can provide information on _most_
resources, there is currently no event triggered after the BuildKit build-cache
is cleaned.

Prune events have a `reclaimed` attribute, indicating the amount of space that
was reclaimed (in bytes). The attribute can be used, for example, to use as a
threshold for performing fstrim actions. Reclaimed space for `network` events
will always be 0, but the field is added to be consistent with prune events for
other resources.

To test this patch:

Create some resources:

for i in foo bar baz; do \
docker network create network_$i \
&& docker volume create volume_$i \
&& docker run -d --name container_$i -v volume_$i:/volume busybox sh -c 'truncate -s 5M somefile; truncate -s 5M /volume/file' \
&& docker tag busybox:latest image_$i; \
done;

docker pull alpine
docker pull nginx:alpine

echo -e "FROM busybox\nRUN truncate -s 50M bigfile" | DOCKER_BUILDKIT=1 docker build -

Start listening for "prune" events in another shell:

docker events --filter event=prune

Prune containers, networks, volumes, and build-cache:

docker system prune -af --volumes

See the events that are returned:

docker events --filter event=prune
2020-07-25T12:12:09.268491000Z container prune (reclaimed=15728640)
2020-07-25T12:12:09.447890400Z network prune (reclaimed=0)
2020-07-25T12:12:09.452323000Z volume prune (reclaimed=15728640)
2020-07-25T12:12:09.517236200Z image prune (reclaimed=21568540)
2020-07-25T12:12:09.566662600Z builder prune (reclaimed=52428841)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2020/07/25 21:14:38
Showing 8 changed files
... ...
@@ -3,12 +3,15 @@ package build // import "github.com/docker/docker/api/server/backend/build"
3 3
 import (
4 4
 	"context"
5 5
 	"fmt"
6
+	"strconv"
6 7
 
7 8
 	"github.com/docker/distribution/reference"
8 9
 	"github.com/docker/docker/api/types"
9 10
 	"github.com/docker/docker/api/types/backend"
11
+	"github.com/docker/docker/api/types/events"
10 12
 	"github.com/docker/docker/builder"
11 13
 	buildkit "github.com/docker/docker/builder/builder-next"
14
+	daemonevents "github.com/docker/docker/daemon/events"
12 15
 	"github.com/docker/docker/image"
13 16
 	"github.com/docker/docker/pkg/stringid"
14 17
 	"github.com/pkg/errors"
... ...
@@ -31,11 +34,12 @@ type Backend struct {
31 31
 	builder        Builder
32 32
 	imageComponent ImageComponent
33 33
 	buildkit       *buildkit.Builder
34
+	eventsService  *daemonevents.Events
34 35
 }
35 36
 
36 37
 // NewBackend creates a new build backend from components
37
-func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder) (*Backend, error) {
38
-	return &Backend{imageComponent: components, builder: builder, buildkit: buildkit}, nil
38
+func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
39
+	return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
39 40
 }
40 41
 
41 42
 // RegisterGRPC registers buildkit controller to the grpc server.
... ...
@@ -100,6 +104,11 @@ func (b *Backend) PruneCache(ctx context.Context, opts types.BuildCachePruneOpti
100 100
 	if err != nil {
101 101
 		return nil, errors.Wrap(err, "failed to prune build cache")
102 102
 	}
103
+	b.eventsService.Log("prune", events.BuilderEventType, events.Actor{
104
+		Attributes: map[string]string{
105
+			"reclaimed": strconv.FormatInt(buildCacheSize, 10),
106
+		},
107
+	})
103 108
 	return &types.BuildCachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
104 109
 }
105 110
 
... ...
@@ -8131,13 +8131,13 @@ paths:
8131 8131
 
8132 8132
         Various objects within Docker report events when something happens to them.
8133 8133
 
8134
-        Containers report these events: `attach`, `commit`, `copy`, `create`, `destroy`, `detach`, `die`, `exec_create`, `exec_detach`, `exec_start`, `exec_die`, `export`, `health_status`, `kill`, `oom`, `pause`, `rename`, `resize`, `restart`, `start`, `stop`, `top`, `unpause`, and `update`
8134
+        Containers report these events: `attach`, `commit`, `copy`, `create`, `destroy`, `detach`, `die`, `exec_create`, `exec_detach`, `exec_start`, `exec_die`, `export`, `health_status`, `kill`, `oom`, `pause`, `rename`, `resize`, `restart`, `start`, `stop`, `top`, `unpause`, `update`, and `prune`
8135 8135
 
8136
-        Images report these events: `delete`, `import`, `load`, `pull`, `push`, `save`, `tag`, and `untag`
8136
+        Images report these events: `delete`, `import`, `load`, `pull`, `push`, `save`, `tag`, `untag`, and `prune`
8137 8137
 
8138
-        Volumes report these events: `create`, `mount`, `unmount`, and `destroy`
8138
+        Volumes report these events: `create`, `mount`, `unmount`, `destroy`, and `prune`
8139 8139
 
8140
-        Networks report these events: `create`, `connect`, `disconnect`, `destroy`, `update`, and `remove`
8140
+        Networks report these events: `create`, `connect`, `disconnect`, `destroy`, `update`, `remove`, and `prune`
8141 8141
 
8142 8142
         The Docker daemon reports these events: `reload`
8143 8143
 
... ...
@@ -8149,6 +8149,8 @@ paths:
8149 8149
 
8150 8150
         Configs report these events: `create`, `update`, and `remove`
8151 8151
 
8152
+        The Builder reports `prune` events
8153
+
8152 8154
       operationId: "SystemEvents"
8153 8155
       produces:
8154 8156
         - "application/json"
... ...
@@ -1,6 +1,8 @@
1 1
 package events // import "github.com/docker/docker/api/types/events"
2 2
 
3 3
 const (
4
+	// BuilderEventType is the event type that the builder generates
5
+	BuilderEventType = "builder"
4 6
 	// ContainerEventType is the event type that containers generate
5 7
 	ContainerEventType = "container"
6 8
 	// DaemonEventType is the event type that daemon generate
... ...
@@ -299,7 +299,7 @@ func newRouterOptions(config *config.Config, d *daemon.Daemon) (routerOptions, e
299 299
 		return opts, err
300 300
 	}
301 301
 
302
-	bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk)
302
+	bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk, d.EventsService)
303 303
 	if err != nil {
304 304
 		return opts, errors.Wrap(err, "failed to create buildmanager")
305 305
 	}
... ...
@@ -3,11 +3,13 @@ package images // import "github.com/docker/docker/daemon/images"
3 3
 import (
4 4
 	"context"
5 5
 	"fmt"
6
+	"strconv"
6 7
 	"sync/atomic"
7 8
 	"time"
8 9
 
9 10
 	"github.com/docker/distribution/reference"
10 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/api/types/events"
11 13
 	"github.com/docker/docker/api/types/filters"
12 14
 	timetypes "github.com/docker/docker/api/types/time"
13 15
 	"github.com/docker/docker/errdefs"
... ...
@@ -159,7 +161,11 @@ deleteImagesLoop:
159 159
 	if canceled {
160 160
 		logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep)
161 161
 	}
162
-
162
+	i.eventsService.Log("prune", events.ImageEventType, events.Actor{
163
+		Attributes: map[string]string{
164
+			"reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10),
165
+		},
166
+	})
163 167
 	return rep, nil
164 168
 }
165 169
 
... ...
@@ -4,10 +4,12 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 	"regexp"
7
+	"strconv"
7 8
 	"sync/atomic"
8 9
 	"time"
9 10
 
10 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/api/types/events"
11 13
 	"github.com/docker/docker/api/types/filters"
12 14
 	timetypes "github.com/docker/docker/api/types/time"
13 15
 	"github.com/docker/docker/errdefs"
... ...
@@ -84,7 +86,9 @@ func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.
84 84
 			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
85 85
 		}
86 86
 	}
87
-
87
+	daemon.EventsService.Log("prune", events.ContainerEventType, events.Actor{
88
+		Attributes: map[string]string{"reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10)},
89
+	})
88 90
 	return rep, nil
89 91
 }
90 92
 
... ...
@@ -210,7 +214,9 @@ func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Ar
210 210
 		return rep, nil
211 211
 	default:
212 212
 	}
213
-
213
+	daemon.EventsService.Log("prune", events.NetworkEventType, events.Actor{
214
+		Attributes: map[string]string{"reclaimed": "0"},
215
+	})
214 216
 	return rep, nil
215 217
 }
216 218
 
... ...
@@ -17,6 +17,10 @@ keywords: "API, Docker, rcli, REST, documentation"
17 17
 
18 18
 [Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation
19 19
 
20
+* `GET /events` now returns `prune` events after pruning resources have completed.
21
+  Prune events are returned for `container`, `network`, `volume`, `image`, and
22
+  `builder`, and have a `reclaimed` attribute, indicating the amount of space
23
+  reclaimed (in bytes).
20 24
 * `GET /info` now returns a `CgroupVersion` field, containing the cgroup version.
21 25
 * `GET /info` now returns a `DefaultAddressPools` field, containing a list of
22 26
   custom default address pools for local networks, which can be specified in the
... ...
@@ -2,6 +2,7 @@ package service // import "github.com/docker/docker/volume/service"
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"strconv"
5 6
 	"sync/atomic"
6 7
 
7 8
 	"github.com/docker/docker/api/types"
... ...
@@ -238,6 +239,9 @@ func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types
238 238
 		rep.SpaceReclaimed += uint64(vSize)
239 239
 		rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name())
240 240
 	}
241
+	s.eventLogger.LogVolumeEvent("", "prune", map[string]string{
242
+		"reclaimed": strconv.FormatInt(int64(rep.SpaceReclaimed), 10),
243
+	})
241 244
 	return rep, nil
242 245
 }
243 246