Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip docker save if docker daemon base image is cached #2049

Merged
merged 17 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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