feat: smaller snapshots by zeroing disk first (#101)

The base image used requires ~0.42Gi. Even if the uploaded image is
smaller, those bytes are currently not overwritten and still part of the
stored snapshot.

By zeroing the root disk first, those unwanted bytes are removed and not
stored with the snapshot.

This has two benefits:

1. Snapshots are billed by their compressed (shown) size, so small
images are now a bit cheaper.
2. The time it takes to create a server from the snapshot scales with
the snapshot size, so smaller snapshots means the server can start more
quickly.

This reduces the size of an example Talos x86 image from 0.42Gi before,
to 0.2Gi afterwards. An example Flatcar image was 0.47Gi before, and
still has that size with this patch.

There are two ways to zero out the disk:

- `dd if=/dev/zero of=/dev/sda` actually writes zeroes to every block on
the device. This takes around a minute to do.
- `blkdiscard /dev/sda` talks to the disk direclty and instructs it to
discard all blocks. This only takes around 5 seconds.

As both have the same effect on image size, but `blkdiscard` is SO MUCH
faster, I have decided to use it.

Even though only small images benefit from this, this is now enabled by
default as the downside (5 second slower upload) does not justify
additional flags or options to enable/disable this.

Closes #96
This commit is contained in:
Julian Tölle 2025-05-10 14:21:31 +02:00 committed by GitHub
parent 420dcf94c9
commit fdfb284533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -329,8 +329,17 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
} }
defer func() { _ = sshClient.Close() }() defer func() { _ = sshClient.Close() }()
// 6. SSH On Server: Download Image, Decompress, Write to Root Disk // 6. Wipe existing disk, to avoid storing any bytes from it in the snapshot
logger.InfoContext(ctx, "# Step 6: Downloading image and writing to disk") logger.InfoContext(ctx, "# Step 6: Cleaning existing disk")
output, err := sshsession.Run(sshClient, "blkdiscard /dev/sda", nil)
logger.DebugContext(ctx, string(output))
if err != nil {
return nil, fmt.Errorf("failed to clean existing disk: %w", err)
}
// 7. SSH On Server: Download Image, Decompress, Write to Root Disk
logger.InfoContext(ctx, "# Step 7: Downloading image and writing to disk")
cmd, err := assembleCommand(options) cmd, err := assembleCommand(options)
if err != nil { if err != nil {
@ -339,23 +348,23 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
logger.DebugContext(ctx, "running download, decompress and write to disk command", "cmd", cmd) logger.DebugContext(ctx, "running download, decompress and write to disk command", "cmd", cmd)
output, err := sshsession.Run(sshClient, cmd, options.ImageReader) output, err = sshsession.Run(sshClient, cmd, options.ImageReader)
logger.InfoContext(ctx, "# Step 6: Finished writing image to disk") logger.InfoContext(ctx, "# Step 7: Finished writing image to disk")
logger.DebugContext(ctx, string(output)) logger.DebugContext(ctx, string(output))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to download and write the image: %w", err) return nil, fmt.Errorf("failed to download and write the image: %w", err)
} }
// 7. SSH On Server: Shutdown // 8. SSH On Server: Shutdown
logger.InfoContext(ctx, "# Step 7: Shutting down server") logger.InfoContext(ctx, "# Step 8: Shutting down server")
_, err = sshsession.Run(sshClient, "shutdown now", nil) _, err = sshsession.Run(sshClient, "shutdown now", nil)
if err != nil { if err != nil {
// TODO Verify if shutdown error, otherwise return // TODO Verify if shutdown error, otherwise return
logger.WarnContext(ctx, "shutdown returned error", "err", err) logger.WarnContext(ctx, "shutdown returned error", "err", err)
} }
// 8. Create Image from Server // 9. Create Image from Server
logger.InfoContext(ctx, "# Step 8: Creating Image") logger.InfoContext(ctx, "# Step 9: Creating Image")
createImageResult, _, err := s.c.Server.CreateImage(ctx, server, &hcloud.ServerCreateImageOpts{ createImageResult, _, err := s.c.Server.CreateImage(ctx, server, &hcloud.ServerCreateImageOpts{
Type: hcloud.ImageTypeSnapshot, Type: hcloud.ImageTypeSnapshot,
Description: options.Description, Description: options.Description,