Skip to content

Commit

Permalink
Use Cloudflare's zlib in Docker images (#81245)
Browse files Browse the repository at this point in the history
Closes #81208. Elasticsearch uses zlib for two purposes:    *
Compression of stored fields with `index.codec: best_compression`,     
which we use for observability and security data.    * Request /
response compression. Historically, zlib was packaged within the JDK, so
that users wouldn't have to have zlib installed for basic usage of Java.
However, the original zlib optimizes for portability and misses a number
of important optimizations such as leveraging vectorization support for
x86 and ARM architectures. Several forks have been created in order to
address this. Since version 9, the JDK uses the system's zlib when
available and falls back to the zlib that is packaged within the JDK if
a system zlib cannot be found. This commit changes the Docker image to
install the Cloudflare fork of zlib, and run Java using the fork instead
of the original zlib, so that users of the Docker image can get better
performance. Other ES distribution types are out-of-scope, since
configuring the JVM to use an alternative zlib requires an environment
config as well as installed another zlib, and Docker is the only
distribution type where we can control both.
  • Loading branch information
pugnascotia authored Dec 3, 2021
1 parent a7fdb08 commit 1f5a0ed
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 14 deletions.
24 changes: 22 additions & 2 deletions distribution/docker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ apply plugin: 'elasticsearch.test.fixtures'
apply plugin: 'elasticsearch.internal-distribution-download'
apply plugin: 'elasticsearch.rest-resources'

ext.cloudflareZlibVersion = '1.2.8'

String buildId = providers.systemProperty('build.id').forUseAtConfigurationTime().getOrNull()
boolean useLocalArtifacts = buildId != null && buildId.isBlank() == false

Expand All @@ -34,6 +36,15 @@ repositories {
content { includeGroup 'krallin' }
}

ivy {
url 'https://github.com/'
patternLayout {
artifact '/[organisation]/[module]/archive/refs/tags/v[revision].[ext]'
}
metadataSources { artifact() }
content { includeGroup 'cloudflare' }
}

// Cloud builds bundle some beats
ivy {
if (useLocalArtifacts) {
Expand Down Expand Up @@ -63,6 +74,7 @@ configurations {
nonRepositoryPlugins
filebeat
metricbeat
cloudflareZlib
}

String beatsArch = Architecture.current() == Architecture.AARCH64 ? 'arm64' : 'x86_64'
Expand All @@ -77,6 +89,7 @@ dependencies {
nonRepositoryPlugins project(path: ':plugins', configuration: 'nonRepositoryPlugins')
filebeat "beats:filebeat:${VersionProperties.elasticsearch}:${beatsArch}@tar.gz"
metricbeat "beats:metricbeat:${VersionProperties.elasticsearch}:${beatsArch}@tar.gz"
cloudflareZlib "cloudflare:zlib:${cloudflareZlibVersion}@tar.gz"
}

ext.expansions = { Architecture architecture, DockerBase base ->
Expand All @@ -100,7 +113,8 @@ ext.expansions = { Architecture architecture, DockerBase base ->
'docker_base' : base.name().toLowerCase(),
'version' : VersionProperties.elasticsearch,
'major_minor_version': "${major}.${minor}",
'retry' : ShellRetry
'retry' : ShellRetry,
'zlib_version' : cloudflareZlibVersion
]
}

Expand Down Expand Up @@ -259,6 +273,7 @@ void addBuildDockerContextTask(Architecture architecture, DockerBase base) {
boolean includeBeats = VersionProperties.isElasticsearchSnapshot() == true || buildId != null

from configurations.repositoryPlugins

if (includeBeats) {
from configurations.filebeat
from configurations.metricbeat
Expand Down Expand Up @@ -289,7 +304,10 @@ void addTransformDockerContextTask(Architecture architecture, DockerBase base) {
from(tarTree("${project.buildDir}/distributions/${archiveName}.tar.gz")) {
eachFile { FileCopyDetails details ->
if (details.name.equals("Dockerfile")) {
filter { it.replaceAll('^RUN curl.*artifacts-no-kpi.*$', "COPY ${distributionName} /tmp/elasticsearch.tar.gz") }
filter { String filename ->
String result = filename.replaceAll('^RUN curl.*artifacts-no-kpi.*$', "COPY ${distributionName} /tmp/elasticsearch.tar.gz")
return result.replaceAll('^RUN curl.*cloudflare/zlib.*$', "COPY zlib-${cloudflareZlibVersion}.tar.gz /tmp")
}
}
}
}
Expand All @@ -302,6 +320,8 @@ void addTransformDockerContextTask(Architecture architecture, DockerBase base) {
from configurations.dockerSource
}

from configurations.cloudflareZlib

if (base == DockerBase.IRON_BANK) {
from (configurations.tini) {
rename { _ -> 'tini' }
Expand Down
50 changes: 38 additions & 12 deletions distribution/docker/src/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,51 @@
*/ %>
<% if (docker_base == 'iron_bank') { %>
################################################################################
# Build stage 0 `builder`:
# Extract Elasticsearch artifact
################################################################################
ARG BASE_REGISTRY=registry1.dso.mil
ARG BASE_IMAGE=ironbank/redhat/ubi/ubi8
ARG BASE_TAG=8.4
<% } %>
################################################################################
# Build stage 0 `zlib`:
# Compile zlib for the current architecture
################################################################################
FROM ${base_image} AS zlib
<% if (docker_base == 'default' || docker_base == 'cloud') { %>
RUN <%= retry.loop(package_manager, "${package_manager} update && DEBIAN_FRONTEND=noninteractive ${package_manager} install -y curl gcc make") %>
<% } else { %>
RUN <%= retry.loop(package_manager, "${package_manager} install -y curl gcc make tar gzip") %>
<% } %>
<%
// Fetch the zlib source. Keep this command on one line - it is replaced
// with a `COPY` during local builds.
%>
WORKDIR /tmp
RUN curl --retry 10 -S -L -o zlib-${zlib_version}.tar.gz https://github.com/cloudflare/zlib/archive/refs/tags/v${zlib_version}.tar.gz
RUN echo "7e393976368975446b263ae4143fb404bc33bf3b436e72007700b5b88e5be332cd461cdec42d31a4b6dffdca2368550f01b9fa1165d81c0aa818bbf2b1ac191e zlib-${zlib_version}.tar.gz" \\
| sha512sum --check -
RUN tar xf zlib-${zlib_version}.tar.gz
WORKDIR /tmp/zlib-${zlib_version}
RUN ./configure --prefix=/usr/local/cloudflare-zlib && make && make install
################################################################################
# Build stage 1 `builder`:
# Extract Elasticsearch artifact
################################################################################
FROM ${base_image} AS builder
<% if (docker_base == 'iron_bank') { %>
# `tini` is a tiny but valid init for containers. This is used to cleanly
# control how ES and any child processes are shut down.
COPY tini /bin/tini
RUN chmod 0555 /bin/tini
<% } else { %>
################################################################################
# Build stage 0 `builder`:
# Extract Elasticsearch artifact
################################################################################
FROM ${base_image} AS builder
# Install required packages to extract the Elasticsearch distribution
<% if (docker_base == 'default' || docker_base == 'cloud') { %>
Expand Down Expand Up @@ -147,9 +170,10 @@ RUN chmod -R 0555 /opt/plugins
<% } %>

################################################################################
# Build stage 1 (the actual Elasticsearch image):
# Build stage 2 (the actual Elasticsearch image):
#
# Copy elasticsearch from stage 0
# Copy zlib from stage 2
# Copy elasticsearch from stage 1
# Add entrypoint
################################################################################

Expand Down Expand Up @@ -207,6 +231,7 @@ WORKDIR /usr/share/elasticsearch
COPY --from=builder --chown=0:0 /usr/share/elasticsearch /usr/share/elasticsearch
COPY --from=builder --chown=0:0 /bin/tini /bin/tini
COPY --from=zlib --chown=0:0 /usr/local/cloudflare-zlib /usr/local/cloudflare-zlib
<% if (docker_base == 'default' || docker_base == 'cloud') { %>
COPY --from=builder --chown=0:0 /etc/ssl/certs/java/cacerts /etc/ssl/certs/java/cacerts
Expand All @@ -217,6 +242,7 @@ COPY --from=builder --chown=0:0 /opt /opt
<% } %>
ENV PATH /usr/share/elasticsearch/bin:\$PATH
ENV ES_ZLIB_PATH /usr/local/cloudflare-zlib/lib
COPY ${bin_dir}/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
Expand Down
8 changes: 8 additions & 0 deletions distribution/src/bin/elasticsearch
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ if [ -z "$LIBFFI_TMPDIR" ]; then
export LIBFFI_TMPDIR
fi

if [ -n "$ES_ZLIB_PATH" ]; then
if [ ! -d "$ES_ZLIB_PATH" ]; then
echo "zlib path specified in ES_ZLIB_PATH does not exist or is not a directory: $ES_ZLIB_PATH" >&2
exit 1
fi
export LD_LIBRARY_PATH="$ES_ZLIB_PATH:$LD_LIBRARY_PATH"
fi

# get keystore password before setting java options to avoid
# conflicting GC configurations for the keystore tools
unset KEYSTORE_PASSWORD
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog/81245.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 81245
summary: Use Cloudflare's zlib in Docker images
area: Packaging
type: enhancement
issues:
- 81208
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,17 @@ public void test050BasicApiTests() throws Exception {
runElasticsearchTestsAsElastic(PASSWORD);
}

/**
* Check that the JDK uses the Cloudflare zlib, instead of the default one.
*/
public void test060JavaUsesCloudflareZlib() {
waitForElasticsearch(installation, "elastic", PASSWORD);

final String output = sh.run("bash -c 'pmap -p $(pidof java)'").stdout;

assertThat("Expected java to be using cloudflare-zlib", output, containsString("cloudflare-zlib"));
}

/**
* Check that the default config can be overridden using a bind mount, and that env vars are respected
*/
Expand Down

0 comments on commit 1f5a0ed

Please sign in to comment.