Skip to content

Commit

Permalink
Skip docker save if docker daemon base image is cached (#2049)
Browse files Browse the repository at this point in the history
  • Loading branch information
TadCordle authored Oct 16, 2019
1 parent d92ddc7 commit 4c0a324
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 4 deletions.
1 change: 1 addition & 0 deletions jib-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.

- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))
- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))
- Docker daemon base images are now cached more effectively, speeding up builds using `DockerDaemonImage` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import com.google.cloud.tools.jib.cache.CachedLayer;
import com.google.cloud.tools.jib.configuration.BuildConfiguration;
import com.google.cloud.tools.jib.docker.DockerClient;
import com.google.cloud.tools.jib.docker.DockerClient.DockerImageDetails;
import com.google.cloud.tools.jib.docker.json.DockerManifestEntryTemplate;
import com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;
import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;
import com.google.cloud.tools.jib.hash.Digests;
import com.google.cloud.tools.jib.http.NotifyingOutputStream;
import com.google.cloud.tools.jib.image.Image;
import com.google.cloud.tools.jib.image.LayerCountMismatchException;
Expand All @@ -48,6 +50,7 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -103,6 +106,13 @@ static Callable<LocalImage> retrieveDockerDaemonImageStep(
new TimerEventDispatcher(
buildConfiguration.getEventHandlers(),
"Saving " + imageReference + " from Docker daemon")) {
DockerClient.DockerImageDetails dockerImageDetails = dockerClient.inspect(imageReference);
Optional<LocalImage> cachedImage =
getCachedDockerImage(buildConfiguration.getBaseImageLayersCache(), dockerImageDetails);
if (cachedImage.isPresent()) {
return cachedImage.get();
}

Path tarPath = tempDirectoryProvider.newDirectory().resolve("out.tar");
long size = dockerClient.inspect(imageReference).getSize();
try (ProgressEventDispatcher dockerProgress =
Expand Down Expand Up @@ -133,6 +143,39 @@ static Callable<LocalImage> retrieveTarImageStep(
buildConfiguration, executorService, tarPath, progressEventDispatcherFactory);
}

@VisibleForTesting
static Optional<LocalImage> getCachedDockerImage(
Cache cache, DockerImageDetails dockerImageDetails)
throws DigestException, IOException, CacheCorruptedException, LayerCountMismatchException,
BadContainerConfigurationFormatException {
V22ManifestTemplate v22Manifest = new V22ManifestTemplate();
List<PreparedLayer> cachedLayers = new ArrayList<>();

// Get config
Optional<ContainerConfigurationTemplate> cachedConfig =
cache.retrieveLocalConfig(dockerImageDetails.getImageId());
if (!cachedConfig.isPresent()) {
return Optional.empty();
}

// Get layers
for (DescriptorDigest diffId : dockerImageDetails.getDiffIds()) {
Optional<CachedLayer> cachedLayer = cache.retrieveTarLayer(diffId);
if (!cachedLayer.isPresent()) {
return Optional.empty();
}
CachedLayer layer = cachedLayer.get();
cachedLayers.add(new PreparedLayer.Builder(layer).build());
v22Manifest.addLayer(layer.getSize(), layer.getDigest());
}

// Create manifest
BlobDescriptor configDescriptor = Digests.computeDigest(cachedConfig.get());
v22Manifest.setContainerConfiguration(configDescriptor.getSize(), configDescriptor.getDigest());
Image image = JsonToImageTranslator.toImage(v22Manifest, cachedConfig.get());
return Optional.of(new LocalImage(image, cachedLayers));
}

@VisibleForTesting
static LocalImage cacheDockerImageTar(
BuildConfiguration buildConfiguration,
Expand All @@ -156,10 +199,12 @@ static LocalImage cacheDockerImageTar(
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.readValue(manifestStream, DockerManifestEntryTemplate[].class)[0];
manifestStream.close();

Path configPath = destination.resolve(loadManifest.getConfig());
ContainerConfigurationTemplate configurationTemplate =
JsonTemplateMapper.readJsonFromFile(
destination.resolve(loadManifest.getConfig()),
ContainerConfigurationTemplate.class);
JsonTemplateMapper.readJsonFromFile(configPath, ContainerConfigurationTemplate.class);
BlobDescriptor originalConfigDescriptor =
Blobs.from(configPath).writeTo(ByteStreams.nullOutputStream());

List<String> layerFiles = loadManifest.getLayerFiles();
if (configurationTemplate.getLayerCount() != layerFiles.size()) {
Expand All @@ -170,6 +215,9 @@ static LocalImage cacheDockerImageTar(
+ configurationTemplate.getLayerCount()
+ " layers");
}
buildConfiguration
.getBaseImageLayersCache()
.writeLocalConfig(originalConfigDescriptor.getDigest(), configurationTemplate);

// Check the first layer to see if the layers are compressed already. 'docker save' output
// is uncompressed, but a jib-built tar has compressed layers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public CachedLayer writeTarLayer(DescriptorDigest diffId, Blob compressedBlob)
* @param containerConfiguration the container configuration
* @throws IOException if an I/O exception occurs
*/
void writeLocalConfig(
public void writeLocalConfig(
DescriptorDigest imageId, ContainerConfigurationTemplate containerConfiguration)
throws IOException {
cacheStorageWriter.writeLocalConfig(imageId, containerConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public static class DockerImageDetails implements JsonTemplate {
private String imageId = "";
private List<String> diffIds = Collections.emptyList();

// Required for JSON
public DockerImageDetails() {}

@VisibleForTesting
public DockerImageDetails(long size, String imageId, List<String> diffIds) {
this.size = size;
this.imageId = imageId;
this.diffIds = diffIds;
}

public long getSize() {
return size;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@
import com.google.cloud.tools.jib.builder.ProgressEventDispatcher;
import com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage;
import com.google.cloud.tools.jib.cache.Cache;
import com.google.cloud.tools.jib.cache.CacheCorruptedException;
import com.google.cloud.tools.jib.configuration.BuildConfiguration;
import com.google.cloud.tools.jib.docker.DockerClient.DockerImageDetails;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.image.LayerCountMismatchException;
import com.google.cloud.tools.jib.image.json.BadContainerConfigurationFormatException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.DigestException;
import java.util.Optional;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -119,6 +128,59 @@ public void testCacheDockerImageTar_validTar() throws Exception {
Assert.assertEquals("value1", result.baseImage.getLabels().get("label1"));
}

@Test
public void testGetCachedDockerImage()
throws IOException, DigestException, BadContainerConfigurationFormatException,
CacheCorruptedException, LayerCountMismatchException, URISyntaxException {
DockerImageDetails dockerImageDetails =
new DockerImageDetails(
0,
"sha256:066872f17ae819f846a6d5abcfc3165abe13fb0a157640fa8cb7af81077670c0",
ImmutableList.of(
"sha256:5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd",
"sha256:f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e"));
Path cachePath = temporaryFolder.newFolder("cache").toPath();
Files.createDirectories(cachePath.resolve("local/config"));
Cache cache = Cache.withDirectory(cachePath);

// Image not in cache
Optional<LocalImage> localImage =
LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);
Assert.assertFalse(localImage.isPresent());

// Config in cache, but not layers
String configHash = "066872f17ae819f846a6d5abcfc3165abe13fb0a157640fa8cb7af81077670c0";
Files.copy(
getResource("core/extraction/test-cache/local/config/" + configHash),
cachePath.resolve("local/config/" + configHash));
localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);
Assert.assertFalse(localImage.isPresent());

// One layer missing
String diffId = "5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd";
String digest = "0011328ac5dfe3dde40c7c5e0e00c98d1833a3aeae2bfb668cf9eb965c229c7f";
Files.createDirectories(cachePath.resolve("local").resolve(diffId));
Files.copy(
getResource("core/extraction/test-cache/local/" + diffId + "/" + digest),
cachePath.resolve("local").resolve(diffId).resolve(digest));
localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);
Assert.assertFalse(localImage.isPresent());

// Image fully in cache
diffId = "f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e";
digest = "c10ef24a5cef5092bbcb5a5666721cff7b86ce978c203a958d1fc86ee6c19f94";
Files.createDirectories(cachePath.resolve("local").resolve(diffId));
Files.copy(
getResource("core/extraction/test-cache/local/" + diffId + "/" + digest),
cachePath.resolve("local").resolve(diffId).resolve(digest));
localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);
Assert.assertTrue(localImage.isPresent());
LocalImage image = localImage.get();
Assert.assertEquals(
ImmutableMap.of("label1", "value1", "label2", "value2"), image.baseImage.getLabels());
Assert.assertEquals(2, image.layers.size());
}

@Test
public void testIsGzipped() throws URISyntaxException, IOException {
Assert.assertTrue(
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"created":"1970-01-01T00:00:01Z","architecture":"amd64","os":"linux","config":{"Env":[],"Entrypoint":["java","-cp","/app/resources:/app/classes:/app/libs/*","Hello"],"ExposedPorts":{},"Labels":{"label1":"value1","label2":"value2"},"Volumes":{}},"history":[{"created":"1970-01-01T00:00:01Z","author":"Jib","created_by":"jib-gradle-plugin:1.4.1-SNAPSHOT","comment":"classes"},{"created":"1970-01-01T00:00:01Z","author":"Jib","created_by":"jib-gradle-plugin:1.4.1-SNAPSHOT","comment":"extra files"}],"rootfs":{"type":"layers","diff_ids":["sha256:5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd","sha256:f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e"]}}
Binary file not shown.
1 change: 1 addition & 0 deletions jib-gradle-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.

- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))
- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))
- Docker daemon base images are now cached more effectively, speeding up builds using `docker://` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions jib-maven-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.

- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))
- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))
- Docker daemon base images are now cached more effectively, speeding up builds using `docker://` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))

### Fixed

Expand Down

0 comments on commit 4c0a324

Please sign in to comment.