From 22e78cef736df31de1acfb194107a5cffde468f2 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 13 Feb 2020 12:59:57 +0000 Subject: [PATCH 01/41] Dump container logs on shell command failure The Docker tests framework captures container logs when Elasticsearch fails to start. However, it doesn't do this if a later shell command fails. Amend the DockerShell wrapper to capture the container logs if a command fails when it should succeed. --- .../org/elasticsearch/packaging/util/Docker.java | 16 ++++++++++++++++ .../org/elasticsearch/packaging/util/Shell.java | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index 3fb6e42e80fd2..d5df21ed3d24f 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -291,6 +291,22 @@ protected String[] getScriptCommand(String script) { return super.getScriptCommand("docker exec --user elasticsearch:root --tty " + containerId + " " + script); } + + @Override + public Result run(String script) { + try { + return super.run(script); + } catch (ShellException e) { + try { + final Shell.Result dockerLogs = getContainerLogs(); + throw new ShellException( + e.getMessage() + "\n\nContainer stdout: " + dockerLogs.stdout + "\n\nContainer stderr: " + dockerLogs.stderr + ); + } catch (ShellException logsException) { + throw new ShellException("Command failed, and couldn't get docker logs", logsException); + } + } + } } /** diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Shell.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Shell.java index d8fd73f6d1ccf..9de4218929bd3 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Shell.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Shell.java @@ -132,7 +132,7 @@ private Result runScript(String[] command) { logger.warn("Running command with env: " + env); Result result = runScriptIgnoreExitCode(command); if (result.isSuccess() == false) { - throw new RuntimeException("Command was not successful: [" + String.join(" ", command) + "]\n result: " + result.toString()); + throw new ShellException("Command was not successful: [" + String.join(" ", command) + "]\n result: " + result.toString()); } return result; } @@ -266,4 +266,17 @@ public String toString() { } } + /** + * An exception to model failures to run a shell command. This exists so that calling code can differentiate between + * shell / command errors, and other runtime errors. + */ + public static class ShellException extends RuntimeException { + public ShellException(String message) { + super(message); + } + + public ShellException(String message, Throwable cause) { + super(message, cause); + } + } } From 1c31c7f017ae2abd684ca9152de2c488ad0114ba Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Fri, 14 Feb 2020 16:29:13 +0000 Subject: [PATCH 02/41] Throw original exception docker logs capture When DockerShell.run() catches a ShellException, dump out the logs and then rethrow the exception, rather than wrapping the original exception. --- .../elasticsearch/packaging/util/Docker.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index d5df21ed3d24f..0c749c2e35173 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -292,6 +292,12 @@ protected String[] getScriptCommand(String script) { return super.getScriptCommand("docker exec --user elasticsearch:root --tty " + containerId + " " + script); } + /** + * Overrides {@link Shell#run(String)} to attempt to collect Docker container + * logs when a command fails to execute successfully. + * @param script the command to run + * @return the command's output + */ @Override public Result run(String script) { try { @@ -299,12 +305,20 @@ public Result run(String script) { } catch (ShellException e) { try { final Shell.Result dockerLogs = getContainerLogs(); - throw new ShellException( - e.getMessage() + "\n\nContainer stdout: " + dockerLogs.stdout + "\n\nContainer stderr: " + dockerLogs.stderr + logger.error( + "Command [{}] failed.\n\nContainer stdout: [{}]\n\nContainer stderr: [{}]", + script, + dockerLogs.stdout, + dockerLogs.stderr + ); + } catch (ShellException shellException) { + logger.error( + "Command [{}] failed.\n\nTried to dump container logs but that failed too: [{}]", + script, + shellException.getMessage() ); - } catch (ShellException logsException) { - throw new ShellException("Command failed, and couldn't get docker logs", logsException); } + throw e; } } } From 545150b0ed2ff53be3556e660bd54b2632bf641a Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 18 Feb 2020 15:11:49 +0000 Subject: [PATCH 03/41] First attempt at building and using a tiny base image --- dev-tools/mkimage-elasticsearch.sh | 165 ++++++++++++++++++ distribution/docker/build.gradle | 3 +- distribution/docker/src/docker/Dockerfile | 51 ++---- .../packaging/test/DockerTests.java | 34 ++-- .../elasticsearch/packaging/util/Docker.java | 21 ++- .../packaging/util/ProcessInfo.java | 50 ++++++ 6 files changed, 260 insertions(+), 64 deletions(-) create mode 100755 dev-tools/mkimage-elasticsearch.sh create mode 100644 qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java diff --git a/dev-tools/mkimage-elasticsearch.sh b/dev-tools/mkimage-elasticsearch.sh new file mode 100755 index 0000000000000..70ad03a2977e4 --- /dev/null +++ b/dev-tools/mkimage-elasticsearch.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +# +# Create a very basic CentOS Docker image for running Elasticsearch. +# +# Originally from: +#  +# https://github.com/moby/moby/blob/master/contrib/mkimage-yum.sh + +set -e + +usage() { + cat < +OPTIONS: + -y The path to the yum config to install packages from. The + default is /etc/yum.conf for Centos/RHEL and /etc/dnf/dnf.conf for Fedora + -t Specify Tag information. + default is reffered at /etc/{redhat,system}-release +EOOPTS + exit 1 +} + +# option defaults +yum_config=/etc/yum.conf +if [ -f /etc/dnf/dnf.conf ] && command -v dnf &> /dev/null; then + yum_config=/etc/dnf/dnf.conf + alias yum=dnf +fi +version= +while getopts ":y:t:h" opt; do + case $opt in + y) + yum_config=$OPTARG + ;; + h) + usage + ;; + t) + version="$OPTARG" + ;; + \?) + echo "Invalid option: -$OPTARG" + usage + ;; + esac +done +shift $((OPTIND - 1)) +name=$1 + +if [[ -z $name ]]; then + usage +fi + +# Create a temporary directory into which we will install files +target=$(mktemp -d --tmpdir $(basename $0).XXXXXX) + +set -x + +# Create required devices +mkdir -m 755 "$target"/dev +mknod -m 600 "$target"/dev/console c 5 1 +mknod -m 600 "$target"/dev/initctl p +mknod -m 666 "$target"/dev/full c 1 7 +mknod -m 666 "$target"/dev/null c 1 3 +mknod -m 666 "$target"/dev/ptmx c 5 2 +mknod -m 666 "$target"/dev/random c 1 8 +mknod -m 666 "$target"/dev/tty c 5 0 +mknod -m 666 "$target"/dev/tty0 c 4 0 +mknod -m 666 "$target"/dev/urandom c 1 9 +mknod -m 666 "$target"/dev/zero c 1 5 + +# Install files. We attempt to install a headless Java distro, and exclude a +# number of unnecessary dependencies. In so doing, we also filter out Java itself, +# but since Elasticsearch ships its own JDK, with its own libs, that isn't a problem +# and in fact is what we want. +# +# Note that I haven't yet verified that these dependencies are, in fact, unnecessary. +# +# We also include some utilities that we ship with the image. +# +# * `pigz` is used for compressing large heaps dumps, and is considerably faster than `gzip` for this task. +# * `tini` is a tiny but valid init for containers. This is used to cleanly control how ES and any child processes are shut down. +# +yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ + --setopt=group_package_types=mandatory -y \ + -x copy-jdk-configs -x cups-libs -x javapackages-tools -x alsa-lib -x freetype -x libjpeg -x libjpeg-turbo \ + --skip-broken \ + install \ + java-latest-openjdk-headless \ + bash zip pigz \ + https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm + +# Use busybox instead of installing more RPMs, which can pulls in all kinds of +# stuff we don't want. Unforunately, there's no RPM available for CentOS. +wget -O "$target"/bin/busybox https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox +chmod +x "$target"/bin/busybox + +# Add links for all the utilities (except sh, as we have bash) +for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do + ln "$target"/bin/busybox "$target"/$path +done + +# This comes from ca-certificates, but this is the only file we want +CA_CERTS=/etc/pki/ca-trust/extracted/java/cacerts +mkdir -p $(dirname "$target"/$CA_CERTS) +cp $CA_CERTS "$target"/$CA_CERTS + +yum -c "$yum_config" --installroot="$target" -y clean all + +# effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target". +# locales +rm -rf "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} +# docs and man pages +rm -rf "$target"/usr/share/{awk,man,doc,info,gnome/help,groff} +# cracklib +rm -rf "$target"/usr/share/cracklib +# i18n +rm -rf "$target"/usr/share/i18n +# yum cache +rm -rf "$target"/var/cache/yum +# sln +rm -rf "$target"/sbin/sln +# ldconfig +rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig +mkdir -p --mode=0755 "$target"/var/cache/ldconfig + +# Remove a bunch of other stuff that isn't required +rm -rf \ + "$target"/etc/yum* \ + "$target"/etc/csh* \ + "$target"/etc/profile* \ + "$target"/etc/skel* \ + "$target"/usr/share/awk \ + "$target"/usr/lib/systemd \ + "$target"/usr/lib/dracut \ + "$target"/var/log/yum.log \ + "$target"/var/lib/yum \ + "$target"/var/lib/rpm \ + "$target"/usr/lib/udev \ + "$target"/etc/groff \ + "$target"/usr/bin/rpm \ + "$target"/usr/bin/tini-static + +if [ -z "$version" ]; then + for file in "$target"/etc/{redhat,system}-release; do + if [ -r "$file" ]; then + version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$file")" + break + fi + done +fi + +if [ -z "$version" ]; then + echo >&2 "warning: cannot autodetect OS version, using '$name' as tag" + version=$name +fi + +# Import the base filesystem into Docker +tar --numeric-owner -c -C "$target" . | docker import - $name:$version + +# Check that the image can be run in a basic fashion +docker run -i -t --rm $name:$version /bin/bash -c 'echo success' + +# Remove the temp dir +rm -rf "$target" diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index f9d12ce242da8..407484aa5b322 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -166,7 +166,8 @@ void addBuildDockerImage(final boolean oss) { ] } executable 'docker' - final List dockerArgs = ['build', buildPath(oss), '--pull', '--no-cache'] + // final List dockerArgs = ['build', buildPath(oss), '--pull', '--no-cache'] + final List dockerArgs = ['build', buildPath(oss)] for (final String tag : tags) { dockerArgs.add('--tag') dockerArgs.add(tag) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index b12a3c7518265..d03b5c7a84c9c 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -11,20 +11,17 @@ # Set gid=0 and make group perms==owner perms ################################################################################ -FROM centos:7 AS builder - -RUN for iter in {1..10}; do yum update --setopt=tsflags=nodocs -y && \ - yum install --setopt=tsflags=nodocs -y gzip shadow-utils tar && \ - yum clean all && exit_code=0 && break || exit_code=\$? && echo "yum error: retry \$iter in 10s" && sleep 10; done; \ - (exit \$exit_code) +FROM elasticsearch/centos-base:7 AS builder ENV PATH /usr/share/elasticsearch/bin:\$PATH -RUN groupadd -g 1000 elasticsearch && \ - adduser -u 1000 -g 1000 -d /usr/share/elasticsearch elasticsearch +RUN addgroup -g 1000 elasticsearch && \ + adduser -D -u 1000 -G elasticsearch -h /usr/share/elasticsearch elasticsearch WORKDIR /usr/share/elasticsearch +# This step may use `curl`, if we're not packaging the locally built artifacts. For that reason, +# this step in the builder doesn't use the `elasticsearch/centos-base` iamge - yet. ${source_elasticsearch} RUN tar zxf /opt/${elasticsearch} --strip-components=1 @@ -35,16 +32,6 @@ RUN chmod 0775 config config/jvm.options.d data logs COPY config/elasticsearch.yml config/log4j2.properties config/ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties -# `tini` is a tiny but valid init for containers. This is used to cleanly -# control how ES and any child processes are shut down. -# -# The tini GitHub page gives instructions for verifying the binary using -# gpg, but the keyservers are slow to return the key and this can fail the -# build. Instead, we check the binary against a checksum that we have -# computed. -ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini /tini -COPY config/tini.sha512 /tini.sha512 -RUN sha512sum -c /tini.sha512 && chmod +x /tini ################################################################################ # Build stage 1 (the actual elasticsearch image): @@ -52,19 +39,13 @@ RUN sha512sum -c /tini.sha512 && chmod +x /tini # Add entrypoint ################################################################################ -FROM centos:7 +FROM elasticsearch/centos-base:7 ENV ELASTIC_CONTAINER true -COPY --from=builder /tini /tini - -RUN for iter in {1..10}; do yum update --setopt=tsflags=nodocs -y && \ - yum install --setopt=tsflags=nodocs -y nc shadow-utils zip unzip && \ - yum clean all && exit_code=0 && break || exit_code=\$? && echo "yum error: retry \$iter in 10s" && sleep 10; done; \ - (exit \$exit_code) - -RUN groupadd -g 1000 elasticsearch && \ - adduser -u 1000 -g 1000 -G 0 -d /usr/share/elasticsearch elasticsearch && \ +RUN addgroup -g 1000 elasticsearch && \ + adduser -D -u 1000 -G elasticsearch -h /usr/share/elasticsearch elasticsearch && \ + addgroup elasticsearch root && \ chmod 0775 /usr/share/elasticsearch && \ chgrp 0 /usr/share/elasticsearch @@ -80,11 +61,12 @@ ENV PATH /usr/share/elasticsearch/bin:\$PATH COPY bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +# 1. Sync the user and group of /etc/passwd +# 2. Set correct permissions of the entrypoint +# 3. Ensure that there are no files with setuid or setgid, in order to mitigate "stackclash" attacks. RUN chmod g=u /etc/passwd && \ - chmod 0775 /usr/local/bin/docker-entrypoint.sh - -# Ensure that there are no files with setuid or setgid, in order to mitigate "stackclash" attacks. -RUN find / -xdev -perm -4000 -exec chmod ug-s {} + + chmod 0775 /usr/local/bin/docker-entrypoint.sh && \ + find / -xdev -perm -4000 -exec chmod ug-s {} + EXPOSE 9200 9300 @@ -110,7 +92,10 @@ LABEL org.label-schema.build-date="${build_date}" \ USER elasticsearch:root -ENTRYPOINT ["/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] +# Our actual entrypoint is `tini`, a minimal but functional init program. It calls our actual entrypoint for us, +# and correctly forwards signals. +ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] + # Dummy overridable parameter parsed by entrypoint CMD ["eswrapper"] diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java index 563a707ccc32e..c39ec20cb2cfe 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java @@ -23,6 +23,7 @@ import org.apache.http.client.fluent.Request; import org.elasticsearch.packaging.util.Installation; import org.elasticsearch.packaging.util.Platforms; +import org.elasticsearch.packaging.util.ProcessInfo; import org.elasticsearch.packaging.util.ServerUtils; import org.elasticsearch.packaging.util.Shell.Result; import org.junit.After; @@ -33,10 +34,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import static java.nio.file.attribute.PosixFilePermissions.fromString; import static org.elasticsearch.packaging.util.Docker.copyFromContainer; @@ -57,13 +56,11 @@ import static org.elasticsearch.packaging.util.FileUtils.getTempDir; import static org.elasticsearch.packaging.util.FileUtils.rm; import static org.elasticsearch.packaging.util.ServerUtils.makeRequest; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -508,7 +505,7 @@ public void test100NoCoreFilesInImage() { * Check that there are no files with a GID other than 0. */ public void test101AllFilesAreGroupZero() { - final String findResults = sh.run("find . -not -gid 0").stdout; + final String findResults = sh.run("find . \\! -group 0").stdout; assertThat("Found some files whose GID != 0", findResults, is(emptyString())); } @@ -603,16 +600,13 @@ public void test120DockerLogsIncludeElasticsearchLogs() throws Exception { * Check that the Java process running inside the container has the expected UID, GID and username. */ public void test130JavaHasCorrectOwnership() { - final List processes = sh.run("ps -o uid,gid,user -C java").stdout.lines().skip(1).collect(Collectors.toList()); + final ProcessInfo info = ProcessInfo.getProcessInfo(sh, "java"); - assertThat("Expected a single java process", processes, hasSize(1)); + assertThat("Incorrect UID", info.uid, equalTo(1000)); + assertThat("Incorrect username", info.username, equalTo("elasticsearch")); - final String[] fields = processes.get(0).trim().split("\\s+"); - - assertThat(fields, arrayWithSize(3)); - assertThat("Incorrect UID", fields[0], equalTo("1000")); - assertThat("Incorrect GID", fields[1], equalTo("0")); - assertThat("Incorrect username", fields[2], equalTo("elasticsearch")); + assertThat("Incorrect GID", info.gid, equalTo(0)); + assertThat("Incorrect group", info.group, equalTo("root")); } /** @@ -620,17 +614,15 @@ public void test130JavaHasCorrectOwnership() { * The PID is particularly important because PID 1 handles signal forwarding and child reaping. */ public void test131InitProcessHasCorrectPID() { - final List processes = sh.run("ps -o pid,uid,gid,user -p 1").stdout.lines().skip(1).collect(Collectors.toList()); + final ProcessInfo info = ProcessInfo.getProcessInfo(sh, "tini"); - assertThat("Expected a single process", processes, hasSize(1)); + assertThat("Incorrect PID", info.pid, equalTo(1)); - final String[] fields = processes.get(0).trim().split("\\s+"); + assertThat("Incorrect UID", info.uid, equalTo(1000)); + assertThat("Incorrect username", info.username, equalTo("elasticsearch")); - assertThat(fields, arrayWithSize(4)); - assertThat("Incorrect PID", fields[0], equalTo("1")); - assertThat("Incorrect UID", fields[1], equalTo("1000")); - assertThat("Incorrect GID", fields[2], equalTo("0")); - assertThat("Incorrect username", fields[3], equalTo("elasticsearch")); + assertThat("Incorrect GID", info.gid, equalTo(0)); + assertThat("Incorrect group", info.group, equalTo("root")); } /** diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index 0c749c2e35173..35cc48e79db35 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -190,7 +190,8 @@ public static void waitForElasticsearchToStart() { // Give the container a chance to crash out Thread.sleep(STARTUP_SLEEP_INTERVAL_MILLISECONDS); - psOutput = dockerShell.run("ps -ww ax").stdout; + // Set COLUMNS so that `ps` doesn't truncate its output + psOutput = dockerShell.run("bash -c 'COLUMNS=2000 ps ax'").stdout; if (psOutput.contains("org.elasticsearch.bootstrap.Elasticsearch")) { isElasticsearchRunning = true; @@ -416,7 +417,7 @@ public static void rmDirWithPrivilegeEscalation(Path localPath) { public static void assertPermissionsAndOwnership(Path path, Set expectedPermissions) { logger.debug("Checking permissions and ownership of [" + path + "]"); - final String[] components = dockerShell.run("stat --format=\"%U %G %A\" " + path).stdout.split("\\s+"); + final String[] components = dockerShell.run("stat -c \"%U %G %A\" " + path).stdout.split("\\s+"); final String username = components[0]; final String group = components[1]; @@ -489,13 +490,15 @@ private static void verifyOssInstallation(Installation es) { Stream.of("LICENSE.txt", "NOTICE.txt", "README.asciidoc").forEach(doc -> assertPermissionsAndOwnership(es.home.resolve(doc), p644)); - // These are installed to help users who are working with certificates. - Stream.of("zip", "unzip").forEach(cliPackage -> { - // We could run `yum list installed $pkg` but that causes yum to call out to the network. - // rpm does the job just as well. - final Shell.Result result = dockerShell.runIgnoreExitCode("rpm -q " + cliPackage); - assertTrue(cliPackage + " ought to be installed. " + result, result.isSuccess()); - }); + // zip/unzip are installed to help users who are working with certificates. + // pigz is useful for compressing large heapdumps more quickly than gzip. + Stream.of("zip", "unzip", "pigz") + .forEach( + cliPackage -> assertTrue( + cliPackage + " ought to be installed. ", + dockerShell.runIgnoreExitCode("which " + cliPackage).isSuccess() + ) + ); } private static void verifyDefaultInstallation(Installation es) { diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java new file mode 100644 index 0000000000000..8c526aa47dd8f --- /dev/null +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java @@ -0,0 +1,50 @@ +package org.elasticsearch.packaging.util; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +public class ProcessInfo { + public final int pid; + public final int uid; + public final int gid; + public final String username; + public final String group; + + public ProcessInfo(int pid, int uid, int gid, String username, String group) { + this.pid = pid; + this.uid = uid; + this.gid = gid; + this.username = username; + this.group = group; + } + + public static ProcessInfo getProcessInfo(Shell sh, String command) { + final List processes = sh.run("pgrep " + command).stdout.lines().collect(Collectors.toList()); + + assertThat("Expected a single process", processes, hasSize(1)); + + // Ensure we actually have a number + final int pid = Integer.parseInt(processes.get(0).trim()); + + int uid = -1; + int gid = -1; + + for (String line : sh.run("cat /proc/" + pid + "/status | grep '^[UG]id:'").stdout.split("\\n")) { + final String[] fields = line.split("\\s+"); + + if (fields[0].equals("Uid:")) { + uid = Integer.parseInt(fields[1]); + } else { + gid = Integer.parseInt(fields[1]); + } + } + + final String username = sh.run("getent passwd " + uid + " | cut -f1 -d:").stdout.trim(); + final String group = sh.run("getent group " + gid + " | cut -f1 -d:").stdout.trim(); + + return new ProcessInfo(pid, uid, gid, username, group); + } +} From 185f2fd0ee3f98dd1cd3861b01a0aa8ed315061d Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 19 Feb 2020 14:51:23 +0000 Subject: [PATCH 04/41] Build the baseimage in Docker --- dev-tools/centos-base/Dockerfile | 16 ++++++++++++++++ dev-tools/centos-base/build.sh | 15 +++++++++++++++ .../{ => centos-base}/mkimage-elasticsearch.sh | 13 ++++++------- distribution/docker/build.gradle | 9 +++++++-- distribution/docker/src/docker/Dockerfile | 9 +++------ 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 dev-tools/centos-base/Dockerfile create mode 100755 dev-tools/centos-base/build.sh rename dev-tools/{ => centos-base}/mkimage-elasticsearch.sh (92%) diff --git a/dev-tools/centos-base/Dockerfile b/dev-tools/centos-base/Dockerfile new file mode 100644 index 0000000000000..7bb455631701f --- /dev/null +++ b/dev-tools/centos-base/Dockerfile @@ -0,0 +1,16 @@ +FROM centos:7 + +# Install docker +RUN yum install -y yum-utils device-mapper-persistent-data lvm2 +RUN yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +RUN yum install -y docker-ce docker-ce-cli containerd.io + +# Required for java-latest-openjdk-headless +RUN yum install -y epel-release + +RUN mkdir /build + +ADD mkimage-elasticsearch.sh /build + +# Update the tag if we move off centos:7 +CMD ["/build/mkimage-elasticsearch.sh", "-t", "7", "elasticsearch/centos-base"] diff --git a/dev-tools/centos-base/build.sh b/dev-tools/centos-base/build.sh new file mode 100755 index 0000000000000..50634109c15aa --- /dev/null +++ b/dev-tools/centos-base/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# This script builds a tiny base image for running Elasticsearch. + +set -ex + +# 1. Build the builder image +docker build -t centos-builder:latest . + +# 2. Run the builder image, giving it access to the local Docker daemon so +# that the tiny base image can be created +docker run -v /var/run/docker.sock:/var/run/docker.sock centos-builder:latest + +# 3. Check that the tiny image is vaguely functional +docker run -i -t --rm elasticsearch/centos-base:7 /bin/bash -c 'echo success' diff --git a/dev-tools/mkimage-elasticsearch.sh b/dev-tools/centos-base/mkimage-elasticsearch.sh similarity index 92% rename from dev-tools/mkimage-elasticsearch.sh rename to dev-tools/centos-base/mkimage-elasticsearch.sh index 70ad03a2977e4..da8240204f90e 100755 --- a/dev-tools/mkimage-elasticsearch.sh +++ b/dev-tools/centos-base/mkimage-elasticsearch.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Create a very basic CentOS Docker image for running Elasticsearch. +# Create a very basic Docker image for running Elasticsearch, using files from CentOS # # Originally from: #  @@ -90,15 +90,17 @@ yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nod bash zip pigz \ https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm -# Use busybox instead of installing more RPMs, which can pulls in all kinds of -# stuff we don't want. Unforunately, there's no RPM available for CentOS. -wget -O "$target"/bin/busybox https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox +# Use busybox instead of installing more RPMs, which can pull in all kinds of +# stuff we don't want. Unforunately, there's no RPM for busybox available for CentOS. +curl -o "$target"/bin/busybox https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox chmod +x "$target"/bin/busybox +set +x # Add links for all the utilities (except sh, as we have bash) for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do ln "$target"/bin/busybox "$target"/$path done +set -x # This comes from ca-certificates, but this is the only file we want CA_CERTS=/etc/pki/ca-trust/extracted/java/cacerts @@ -158,8 +160,5 @@ fi # Import the base filesystem into Docker tar --numeric-owner -c -C "$target" . | docker import - $name:$version -# Check that the image can be run in a basic fashion -docker run -i -t --rm $name:$version /bin/bash -c 'echo success' - # Remove the temp dir rm -rf "$target" diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 407484aa5b322..c2364dad195ca 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -24,13 +24,18 @@ dependencies { ext.expansions = { oss, local -> final String classifier = 'linux-x86_64' - final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" + final String elasticsearch = oss + ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" + : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" + return [ 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, 'git_revision' : BuildParams.gitRevision, 'license' : oss ? 'Apache-2.0' : 'Elastic-License', - 'source_elasticsearch': local ? "COPY $elasticsearch /opt/" : "RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/elasticsearch/${elasticsearch} && cd -", + 'source_elasticsearch': local + ? "COPY $elasticsearch /opt/" + : "RUN cd /opt && wget https://artifacts.elastic.co/downloads/elasticsearch/${elasticsearch}", 'version' : VersionProperties.elasticsearch ] } diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index d03b5c7a84c9c..d12f057bd004a 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -20,13 +20,10 @@ RUN addgroup -g 1000 elasticsearch && \ WORKDIR /usr/share/elasticsearch -# This step may use `curl`, if we're not packaging the locally built artifacts. For that reason, -# this step in the builder doesn't use the `elasticsearch/centos-base` iamge - yet. ${source_elasticsearch} RUN tar zxf /opt/${elasticsearch} --strip-components=1 -RUN grep ES_DISTRIBUTION_TYPE=tar /usr/share/elasticsearch/bin/elasticsearch-env \ - && sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' /usr/share/elasticsearch/bin/elasticsearch-env +RUN sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' /usr/share/elasticsearch/bin/elasticsearch-env RUN mkdir -p config config/jvm.options.d data logs RUN chmod 0775 config config/jvm.options.d data logs COPY config/elasticsearch.yml config/log4j2.properties config/ @@ -92,8 +89,8 @@ LABEL org.label-schema.build-date="${build_date}" \ USER elasticsearch:root -# Our actual entrypoint is `tini`, a minimal but functional init program. It calls our actual entrypoint for us, -# and correctly forwards signals. +# Our actual entrypoint is `tini`, a minimal but functional init program. It +# calls the actual entrypoint for us, and correctly forwards signals. ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] # Dummy overridable parameter parsed by entrypoint From 032eceab55ff3f235ca5a0b1155701fde6d8fdd4 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 30 Mar 2020 20:21:57 +0100 Subject: [PATCH 05/41] Add Dockerfile for statically building curl --- dev-tools/curl-minimal/Dockerfile | 75 +++++++++++++++++++++++++++++++ dev-tools/curl-minimal/build.sh | 9 ++++ 2 files changed, 84 insertions(+) create mode 100644 dev-tools/curl-minimal/Dockerfile create mode 100755 dev-tools/curl-minimal/build.sh diff --git a/dev-tools/curl-minimal/Dockerfile b/dev-tools/curl-minimal/Dockerfile new file mode 100644 index 0000000000000..07ccda0489033 --- /dev/null +++ b/dev-tools/curl-minimal/Dockerfile @@ -0,0 +1,75 @@ +FROM centos:7 AS builder + +ENV LDFLAGS=-static + +RUN mkdir /work + +RUN yum update -y +RUN yum install -y \ + file \ + gcc \ + glibc-static \ + make \ + perl + +# Static libz +WORKDIR /work +RUN curl https://www.zlib.net/zlib-1.2.11.tar.gz > zlib-1.2.11.tar.gz \ + && tar xf zlib-1.2.11.tar.gz +WORKDIR /work/zlib-1.2.11 +RUN ./configure --static \ + && make \ + && make install + +# Static openssl +WORKDIR /work +RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz > openssl-1.1.1e.tar.gz +RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz.sha1 > openssl-1.1.1e.tar.gz.sha1 +# The OpenSSL checksum doesn't include the filename, so we can't use `sha1sum -c` +RUN sha1sum openssl-1.1.1e.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1e.tar.gz.sha1 +RUN tar xf openssl-1.1.1e.tar.gz +WORKDIR /work/openssl-1.1.1e +RUN ./config -v no-shared \ + && make \ + && make install + +# Static curl +WORKDIR /work +RUN curl https://curl.haxx.se/download/curl-7.69.1.tar.gz > curl-7.69.1.tar.gz \ + && tar xf curl-7.69.1.tar.gz +WORKDIR /work/curl-7.69.1 +RUN ./configure \ + --with-ssl=/usr/local \ + --disable-shared \ + --enable-static \ + --prefix=/tmp/curl \ + --disable-ldap \ + --disable-sspi \ + --without-librtmp \ + --disable-ftp \ + --disable-file \ + --disable-dict \ + --disable-telnet \ + --disable-tftp \ + --disable-rtsp \ + --disable-pop3 \ + --disable-imap \ + --disable-smtp \ + --disable-gopher \ + --disable-smb \ + --without-libidn \ + --disable-ares \ + && make \ + && make install \ + && strip /tmp/curl/bin/curl + + +#------------------------------------------------------------------------------- + +# HACK +FROM centos:7 + +COPY --from=builder /tmp/curl/bin/curl /curl +COPY --from=builder /etc/pki /etc/pki + +CMD curl diff --git a/dev-tools/curl-minimal/build.sh b/dev-tools/curl-minimal/build.sh new file mode 100755 index 0000000000000..2cd6708822b9e --- /dev/null +++ b/dev-tools/curl-minimal/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -exo pipefail + +IMAGE=curl-minimal:latest + +docker build -t $IMAGE . + +docker run -it --rm $IMAGE /curl https://www.google.com/ From 20563741676ecb7aa97a40a30c4e6aefbf6b103a Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 30 Mar 2020 22:20:21 +0100 Subject: [PATCH 06/41] Merge curl building into tiny image build --- dev-tools/centos-base/Dockerfile | 70 +++++++++++++++++ .../centos-base/mkimage-elasticsearch.sh | 8 +- dev-tools/curl-minimal/Dockerfile | 75 ------------------- dev-tools/curl-minimal/build.sh | 9 --- 4 files changed, 73 insertions(+), 89 deletions(-) delete mode 100644 dev-tools/curl-minimal/Dockerfile delete mode 100755 dev-tools/curl-minimal/build.sh diff --git a/dev-tools/centos-base/Dockerfile b/dev-tools/centos-base/Dockerfile index 7bb455631701f..708b0484dc512 100644 --- a/dev-tools/centos-base/Dockerfile +++ b/dev-tools/centos-base/Dockerfile @@ -1,3 +1,72 @@ +# Phase 1 - More-or-less statically build curl so that we don't have to +# install all the dependencies that the CentOS version pulls in. + +FROM centos:7 AS builder + +ENV LDFLAGS=-static + +RUN mkdir /work + +RUN yum update -y +RUN yum install -y \ + file \ + gcc \ + glibc-static \ + make \ + perl + +# Static libz +WORKDIR /work +RUN curl https://www.zlib.net/zlib-1.2.11.tar.gz > zlib-1.2.11.tar.gz \ + && tar xf zlib-1.2.11.tar.gz +WORKDIR /work/zlib-1.2.11 +RUN ./configure --static \ + && make \ + && make install + +# Static openssl +WORKDIR /work +RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz > openssl-1.1.1e.tar.gz +RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz.sha1 > openssl-1.1.1e.tar.gz.sha1 +# The OpenSSL checksum doesn't include the filename, so we can't use `sha1sum -c` +RUN sha1sum openssl-1.1.1e.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1e.tar.gz.sha1 +RUN tar xf openssl-1.1.1e.tar.gz +WORKDIR /work/openssl-1.1.1e +RUN ./config -v no-shared \ + && make \ + && make install + +# Static curl +WORKDIR /work +RUN curl https://curl.haxx.se/download/curl-7.69.1.tar.gz > curl-7.69.1.tar.gz \ + && tar xf curl-7.69.1.tar.gz +WORKDIR /work/curl-7.69.1 +RUN ./configure \ + --with-ssl=/usr/local \ + --disable-shared \ + --enable-static \ + --prefix=/tmp/curl \ + --disable-ldap \ + --disable-sspi \ + --without-librtmp \ + --disable-ftp \ + --disable-file \ + --disable-dict \ + --disable-telnet \ + --disable-tftp \ + --disable-rtsp \ + --disable-pop3 \ + --disable-imap \ + --disable-smtp \ + --disable-gopher \ + --disable-smb \ + --without-libidn \ + --disable-ares \ + && make \ + && make install \ + && strip /tmp/curl/bin/curl + +# Phase 2 - build an image that can be executed to build a minimal image. FROM centos:7 # Install docker @@ -11,6 +80,7 @@ RUN yum install -y epel-release RUN mkdir /build ADD mkimage-elasticsearch.sh /build +COPY --from=builder /tmp/curl/bin/curl /build/curl # Update the tag if we move off centos:7 CMD ["/build/mkimage-elasticsearch.sh", "-t", "7", "elasticsearch/centos-base"] diff --git a/dev-tools/centos-base/mkimage-elasticsearch.sh b/dev-tools/centos-base/mkimage-elasticsearch.sh index da8240204f90e..87867f2b3dbee 100755 --- a/dev-tools/centos-base/mkimage-elasticsearch.sh +++ b/dev-tools/centos-base/mkimage-elasticsearch.sh @@ -98,14 +98,12 @@ chmod +x "$target"/bin/busybox set +x # Add links for all the utilities (except sh, as we have bash) for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do - ln "$target"/bin/busybox "$target"/$path + ln -s "$target"/bin/busybox "$target"/$path done set -x -# This comes from ca-certificates, but this is the only file we want -CA_CERTS=/etc/pki/ca-trust/extracted/java/cacerts -mkdir -p $(dirname "$target"/$CA_CERTS) -cp $CA_CERTS "$target"/$CA_CERTS +cp /build/curl "$target"/usr/bin/curl +tar cf - /etc/pki | (cd "$target" && tar xf -) yum -c "$yum_config" --installroot="$target" -y clean all diff --git a/dev-tools/curl-minimal/Dockerfile b/dev-tools/curl-minimal/Dockerfile deleted file mode 100644 index 07ccda0489033..0000000000000 --- a/dev-tools/curl-minimal/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ -FROM centos:7 AS builder - -ENV LDFLAGS=-static - -RUN mkdir /work - -RUN yum update -y -RUN yum install -y \ - file \ - gcc \ - glibc-static \ - make \ - perl - -# Static libz -WORKDIR /work -RUN curl https://www.zlib.net/zlib-1.2.11.tar.gz > zlib-1.2.11.tar.gz \ - && tar xf zlib-1.2.11.tar.gz -WORKDIR /work/zlib-1.2.11 -RUN ./configure --static \ - && make \ - && make install - -# Static openssl -WORKDIR /work -RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz > openssl-1.1.1e.tar.gz -RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz.sha1 > openssl-1.1.1e.tar.gz.sha1 -# The OpenSSL checksum doesn't include the filename, so we can't use `sha1sum -c` -RUN sha1sum openssl-1.1.1e.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1e.tar.gz.sha1 -RUN tar xf openssl-1.1.1e.tar.gz -WORKDIR /work/openssl-1.1.1e -RUN ./config -v no-shared \ - && make \ - && make install - -# Static curl -WORKDIR /work -RUN curl https://curl.haxx.se/download/curl-7.69.1.tar.gz > curl-7.69.1.tar.gz \ - && tar xf curl-7.69.1.tar.gz -WORKDIR /work/curl-7.69.1 -RUN ./configure \ - --with-ssl=/usr/local \ - --disable-shared \ - --enable-static \ - --prefix=/tmp/curl \ - --disable-ldap \ - --disable-sspi \ - --without-librtmp \ - --disable-ftp \ - --disable-file \ - --disable-dict \ - --disable-telnet \ - --disable-tftp \ - --disable-rtsp \ - --disable-pop3 \ - --disable-imap \ - --disable-smtp \ - --disable-gopher \ - --disable-smb \ - --without-libidn \ - --disable-ares \ - && make \ - && make install \ - && strip /tmp/curl/bin/curl - - -#------------------------------------------------------------------------------- - -# HACK -FROM centos:7 - -COPY --from=builder /tmp/curl/bin/curl /curl -COPY --from=builder /etc/pki /etc/pki - -CMD curl diff --git a/dev-tools/curl-minimal/build.sh b/dev-tools/curl-minimal/build.sh deleted file mode 100755 index 2cd6708822b9e..0000000000000 --- a/dev-tools/curl-minimal/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -exo pipefail - -IMAGE=curl-minimal:latest - -docker build -t $IMAGE . - -docker run -it --rm $IMAGE /curl https://www.google.com/ From 5701d917b02bb25c9fac6072078dae3c53a38a2a Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 8 Apr 2020 12:06:29 +0100 Subject: [PATCH 07/41] Tweaks --- .../java/org/elasticsearch/gradle/test/DistroTestPlugin.java | 1 - distribution/docker/build.gradle | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/test/DistroTestPlugin.java b/buildSrc/src/main/java/org/elasticsearch/gradle/test/DistroTestPlugin.java index a21347e9d2cb4..af3b1ff598c87 100644 --- a/buildSrc/src/main/java/org/elasticsearch/gradle/test/DistroTestPlugin.java +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/test/DistroTestPlugin.java @@ -155,7 +155,6 @@ public void apply(Project project) { // // The shouldTestDocker property could be null, hence we use Boolean.TRUE.equals() boolean shouldExecute = distribution.getType() != Type.DOCKER - || Boolean.TRUE.equals(vmProject.findProperty("shouldTestDocker")); if (shouldExecute) { diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index eeaa6a61d1d45..c24b19cc71fa1 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -38,7 +38,7 @@ ext.expansions = { architecture, oss, local -> final String classifier = "aarch64".equals(architecture) ? "linux-aarch64" : "linux-x86_64" final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" return [ - 'base_image' : "elastic/baseimage:20200406-${'aarch64'.equals(architecture) ? 'arm64' : 'amd64'}", + 'base_image' : "elastic/baseimage:20200407-${'aarch64'.equals(architecture) ? 'arm64' : 'amd64'}", 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, 'git_revision' : BuildParams.gitRevision, @@ -177,6 +177,8 @@ void addBuildDockerImage(final String architecture, final boolean oss) { dependsOn(copyContextTask) dockerContext.fileProvider(copyContextTask.map { it.destinationDir }) + pull = false + if (oss) { tags = [ "docker.elastic.co/elasticsearch/elasticsearch-oss:${VersionProperties.elasticsearch}", From f47ba97ddcc5c37a0a2d8d77a8ac7fee65b949f1 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 14 Apr 2020 15:41:51 +0100 Subject: [PATCH 08/41] Integrate building a base image into the Docker build --- dev-tools/centos-base/Dockerfile | 86 ------------- dev-tools/centos-base/build.sh | 15 --- distribution/docker/build.gradle | 29 ++++- distribution/docker/src/docker/Dockerfile | 9 +- .../src/docker/bin/install-baseimage.sh | 118 +++++++++--------- 5 files changed, 91 insertions(+), 166 deletions(-) delete mode 100644 dev-tools/centos-base/Dockerfile delete mode 100755 dev-tools/centos-base/build.sh rename dev-tools/centos-base/mkimage-elasticsearch.sh => distribution/docker/src/docker/bin/install-baseimage.sh (59%) diff --git a/dev-tools/centos-base/Dockerfile b/dev-tools/centos-base/Dockerfile deleted file mode 100644 index 708b0484dc512..0000000000000 --- a/dev-tools/centos-base/Dockerfile +++ /dev/null @@ -1,86 +0,0 @@ -# Phase 1 - More-or-less statically build curl so that we don't have to -# install all the dependencies that the CentOS version pulls in. - -FROM centos:7 AS builder - -ENV LDFLAGS=-static - -RUN mkdir /work - -RUN yum update -y -RUN yum install -y \ - file \ - gcc \ - glibc-static \ - make \ - perl - -# Static libz -WORKDIR /work -RUN curl https://www.zlib.net/zlib-1.2.11.tar.gz > zlib-1.2.11.tar.gz \ - && tar xf zlib-1.2.11.tar.gz -WORKDIR /work/zlib-1.2.11 -RUN ./configure --static \ - && make \ - && make install - -# Static openssl -WORKDIR /work -RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz > openssl-1.1.1e.tar.gz -RUN curl https://www.openssl.org/source/openssl-1.1.1e.tar.gz.sha1 > openssl-1.1.1e.tar.gz.sha1 -# The OpenSSL checksum doesn't include the filename, so we can't use `sha1sum -c` -RUN sha1sum openssl-1.1.1e.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1e.tar.gz.sha1 -RUN tar xf openssl-1.1.1e.tar.gz -WORKDIR /work/openssl-1.1.1e -RUN ./config -v no-shared \ - && make \ - && make install - -# Static curl -WORKDIR /work -RUN curl https://curl.haxx.se/download/curl-7.69.1.tar.gz > curl-7.69.1.tar.gz \ - && tar xf curl-7.69.1.tar.gz -WORKDIR /work/curl-7.69.1 -RUN ./configure \ - --with-ssl=/usr/local \ - --disable-shared \ - --enable-static \ - --prefix=/tmp/curl \ - --disable-ldap \ - --disable-sspi \ - --without-librtmp \ - --disable-ftp \ - --disable-file \ - --disable-dict \ - --disable-telnet \ - --disable-tftp \ - --disable-rtsp \ - --disable-pop3 \ - --disable-imap \ - --disable-smtp \ - --disable-gopher \ - --disable-smb \ - --without-libidn \ - --disable-ares \ - && make \ - && make install \ - && strip /tmp/curl/bin/curl - -# Phase 2 - build an image that can be executed to build a minimal image. -FROM centos:7 - -# Install docker -RUN yum install -y yum-utils device-mapper-persistent-data lvm2 -RUN yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -RUN yum install -y docker-ce docker-ce-cli containerd.io - -# Required for java-latest-openjdk-headless -RUN yum install -y epel-release - -RUN mkdir /build - -ADD mkimage-elasticsearch.sh /build -COPY --from=builder /tmp/curl/bin/curl /build/curl - -# Update the tag if we move off centos:7 -CMD ["/build/mkimage-elasticsearch.sh", "-t", "7", "elasticsearch/centos-base"] diff --git a/dev-tools/centos-base/build.sh b/dev-tools/centos-base/build.sh deleted file mode 100755 index 50634109c15aa..0000000000000 --- a/dev-tools/centos-base/build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# This script builds a tiny base image for running Elasticsearch. - -set -ex - -# 1. Build the builder image -docker build -t centos-builder:latest . - -# 2. Run the builder image, giving it access to the local Docker daemon so -# that the tiny base image can be created -docker run -v /var/run/docker.sock:/var/run/docker.sock centos-builder:latest - -# 3. Check that the tiny image is vaguely functional -docker run -i -t --rm elasticsearch/centos-base:7 /bin/bash -c 'echo success' diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index c24b19cc71fa1..339859f92f05d 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -38,7 +38,7 @@ ext.expansions = { architecture, oss, local -> final String classifier = "aarch64".equals(architecture) ? "linux-aarch64" : "linux-x86_64" final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" return [ - 'base_image' : "elastic/baseimage:20200407-${'aarch64'.equals(architecture) ? 'arm64' : 'amd64'}", + 'base_image_tar' : "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar", 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, 'git_revision' : BuildParams.gitRevision, @@ -78,12 +78,19 @@ project.ext { from(project.projectDir.toPath().resolve("src/docker/Dockerfile")) { expand(expansions(architecture, oss, local)) } + + from("${projectDir}/build") { + include "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar" + } } } } void addCopyDockerContextTask(final String architecture, final boolean oss) { task(taskName("copy", architecture, oss, "DockerContext"), type: Sync) { + TaskProvider buildBaseImageTask = tasks.named(taskName("build", architecture, oss, "DockerBaseImage")) + dependsOn(buildBaseImageTask) + expansions(architecture, oss, true).findAll { it.key != 'build_date' }.each { k, v -> inputs.property(k, { v.toString() }) } @@ -171,10 +178,29 @@ task integTest(type: Test) { check.dependsOn integTest +void addBuildDockerBaseImage(final String architecture, final boolean oss) { + def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" + def outputFile = "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar" + def outputPath = "${projectDir}/build/${outputFile}" + def archArg = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' + + final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { + outputs.file(outputPath) + executable 'bash' + args '-c', "docker run --rm -t " + + "-v ${projectDir}/build:/build " + + "-v ${installer}:/tmp/install-baseimage.sh " + + "centos:7 /tmp/install-baseimage.sh ${archArg} /build/${outputFile}" + } + + buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } +} + void addBuildDockerImage(final String architecture, final boolean oss) { final Task buildDockerImageTask = task(taskName("build", architecture, oss, "DockerImage"), type: DockerBuildTask) { TaskProvider copyContextTask = tasks.named(taskName("copy", architecture, oss, "DockerContext")) dependsOn(copyContextTask) + dockerContext.fileProvider(copyContextTask.map { it.destinationDir }) pull = false @@ -201,6 +227,7 @@ void addBuildDockerImage(final String architecture, final boolean oss) { for (final String architecture : ["aarch64", "x64"]) { for (final boolean oss : [false, true]) { + addBuildDockerBaseImage(architecture, oss) addCopyDockerContextTask(architecture, oss) addBuildDockerImage(architecture, oss) } diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 2d30ec496f787..da36b7557e4a1 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -11,7 +11,9 @@ # Set gid=0 and make group perms==owner perms ################################################################################ -FROM ${base_image} AS builder +FROM scratch AS builder + +ADD ${base_image_tar} / ENV PATH /usr/share/elasticsearch/bin:\$PATH @@ -29,14 +31,15 @@ RUN chmod 0775 config config/jvm.options.d data logs COPY config/elasticsearch.yml config/log4j2.properties config/ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties - ################################################################################ # Build stage 1 (the actual elasticsearch image): # Copy elasticsearch from stage 0 # Add entrypoint ################################################################################ -FROM ${base_image} +FROM scratch + +ADD ${base_image_tar} / ENV ELASTIC_CONTAINER true diff --git a/dev-tools/centos-base/mkimage-elasticsearch.sh b/distribution/docker/src/docker/bin/install-baseimage.sh similarity index 59% rename from dev-tools/centos-base/mkimage-elasticsearch.sh rename to distribution/docker/src/docker/bin/install-baseimage.sh index 87867f2b3dbee..f7bf8f45eedac 100755 --- a/dev-tools/centos-base/mkimage-elasticsearch.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # -# Create a very basic Docker image for running Elasticsearch, using files from CentOS +# Create a very basic Docker image that can be extended for running Elastic +# Stack images. # # Originally from: #  @@ -10,44 +11,19 @@ set -e usage() { cat < -OPTIONS: - -y The path to the yum config to install packages from. The - default is /etc/yum.conf for Centos/RHEL and /etc/dnf/dnf.conf for Fedora - -t Specify Tag information. - default is reffered at /etc/{redhat,system}-release +$(basename $0) EOOPTS exit 1 } -# option defaults -yum_config=/etc/yum.conf -if [ -f /etc/dnf/dnf.conf ] && command -v dnf &> /dev/null; then - yum_config=/etc/dnf/dnf.conf - alias yum=dnf +platform="$1" +output_file="$2" + +if [[ -z $platform ]]; then + usage fi -version= -while getopts ":y:t:h" opt; do - case $opt in - y) - yum_config=$OPTARG - ;; - h) - usage - ;; - t) - version="$OPTARG" - ;; - \?) - echo "Invalid option: -$OPTARG" - usage - ;; - esac -done -shift $((OPTIND - 1)) -name=$1 -if [[ -z $name ]]; then +if [[ -z "$output_file" ]]; then usage fi @@ -69,11 +45,21 @@ mknod -m 666 "$target"/dev/tty0 c 4 0 mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 +ARCH="$(arch)" + +TINI_URL="" +if [[ "$ARCH" == "x86_64" ]]; then + TINI_URL="https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm" +fi + # Install files. We attempt to install a headless Java distro, and exclude a # number of unnecessary dependencies. In so doing, we also filter out Java itself, # but since Elasticsearch ships its own JDK, with its own libs, that isn't a problem # and in fact is what we want. # +# Note that we also skip coreutils, as it pulls in all kinds of stuff that +# we don't want. +# # Note that I haven't yet verified that these dependencies are, in fact, unnecessary. # # We also include some utilities that we ship with the image. @@ -81,37 +67,53 @@ mknod -m 666 "$target"/dev/zero c 1 5 # * `pigz` is used for compressing large heaps dumps, and is considerably faster than `gzip` for this task. # * `tini` is a tiny but valid init for containers. This is used to cleanly control how ES and any child processes are shut down. # -yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ +yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y \ - -x copy-jdk-configs -x cups-libs -x javapackages-tools -x alsa-lib -x freetype -x libjpeg -x libjpeg-turbo \ + -x copy-jdk-configs -x cups-libs -x javapackages-tools -x alsa-lib -x freetype -x libjpeg -x libjpeg-turbo \ + -x coreutils \ --skip-broken \ install \ java-latest-openjdk-headless \ bash zip pigz \ - https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm + $TINI_URL + +if [[ "$ARCH" == "aarch64" ]]; then + curl --retry 10 -L -o "$target"/bin/tini https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-arm64 + chmod +x "$target"/bin/tini +fi # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. Unforunately, there's no RPM for busybox available for CentOS. -curl -o "$target"/bin/busybox https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox +BUSYBOX_URL="https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox" +if [[ "$ARCH" == "aarch64" ]]; then + BUSYBOX_URL="https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv8l" +fi +curl --retry 10 -L -o "$target"/bin/busybox "$BUSYBOX_URL" chmod +x "$target"/bin/busybox set +x # Add links for all the utilities (except sh, as we have bash) for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do - ln -s "$target"/bin/busybox "$target"/$path + ln "$target"/bin/busybox "$target"/$path done set -x -cp /build/curl "$target"/usr/bin/curl +# Copy in our mostly-static curl build +# TODO +# cp /build/curl "$target"/usr/bin/curl + +# Curl needs files under here. More importantly, e.g. we change Elasticsearch's +# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of +# the bundled cacerts. tar cf - /etc/pki | (cd "$target" && tar xf -) -yum -c "$yum_config" --installroot="$target" -y clean all +yum --installroot="$target" -y clean all # effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target". # locales rm -rf "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} # docs and man pages -rm -rf "$target"/usr/share/{awk,man,doc,info,gnome/help,groff} +rm -rf "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons} # cracklib rm -rf "$target"/usr/share/cracklib # i18n @@ -128,8 +130,10 @@ mkdir -p --mode=0755 "$target"/var/cache/ldconfig rm -rf \ "$target"/etc/yum* \ "$target"/etc/csh* \ + "$target"/etc/centos-release* \ "$target"/etc/profile* \ "$target"/etc/skel* \ + "$target"/etc/X11 \ "$target"/usr/share/awk \ "$target"/usr/lib/systemd \ "$target"/usr/lib/dracut \ @@ -137,26 +141,18 @@ rm -rf \ "$target"/var/lib/yum \ "$target"/var/lib/rpm \ "$target"/usr/lib/udev \ + "$target"/usr/share/centos-release \ + "$target"/usr/share/desktop-directories \ + "$target"/usr/share/gcc-* \ + "$target"/usr/share/icons \ + "$target"/usr/share/licenses \ + "$target"/usr/share/xsessions \ + "$target"/usr/share/zoneinfo \ "$target"/etc/groff \ "$target"/usr/bin/rpm \ - "$target"/usr/bin/tini-static - -if [ -z "$version" ]; then - for file in "$target"/etc/{redhat,system}-release; do - if [ -r "$file" ]; then - version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$file")" - break - fi - done -fi - -if [ -z "$version" ]; then - echo >&2 "warning: cannot autodetect OS version, using '$name' as tag" - version=$name -fi - -# Import the base filesystem into Docker -tar --numeric-owner -c -C "$target" . | docker import - $name:$version + "$target"/usr/bin/tini-static \ + "$target"/usr/local -# Remove the temp dir -rm -rf "$target" +# Write the base filesystem to stdout. The command changes to $target, so . +# refers to that directory +tar --numeric-owner -c -f "$output_file" -C "$target" . From 95db73f2979b7d8f5458995faef0473857dbdaed Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 14 Apr 2020 21:56:32 +0100 Subject: [PATCH 09/41] Allow a platform to be specified --- distribution/docker/build.gradle | 9 +++------ distribution/docker/src/docker/Dockerfile | 4 ++-- .../docker/src/docker/bin/install-baseimage.sh | 16 +++++++--------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 339859f92f05d..b1855cc2b7c73 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -38,6 +38,7 @@ ext.expansions = { architecture, oss, local -> final String classifier = "aarch64".equals(architecture) ? "linux-aarch64" : "linux-x86_64" final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" return [ + 'platform' : "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64', 'base_image_tar' : "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar", 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, @@ -203,8 +204,6 @@ void addBuildDockerImage(final String architecture, final boolean oss) { dockerContext.fileProvider(copyContextTask.map { it.destinationDir }) - pull = false - if (oss) { tags = [ "docker.elastic.co/elasticsearch/elasticsearch-oss:${VersionProperties.elasticsearch}", @@ -219,10 +218,8 @@ void addBuildDockerImage(final String architecture, final boolean oss) { ] } } - if (Architecture.current().name().toLowerCase().equals(architecture)) { -// buildDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } - assemble.dependsOn(buildDockerImageTask) - } + buildDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } + assemble.dependsOn(buildDockerImageTask) } for (final String architecture : ["aarch64", "x64"]) { diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index da36b7557e4a1..c68ac04d7c238 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -11,7 +11,7 @@ # Set gid=0 and make group perms==owner perms ################################################################################ -FROM scratch AS builder +FROM --platform=${platform} scratch AS builder ADD ${base_image_tar} / @@ -37,7 +37,7 @@ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties # Add entrypoint ################################################################################ -FROM scratch +FROM --platform=${platform} scratch ADD ${base_image_tar} / diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index f7bf8f45eedac..7f6a338562f61 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Create a very basic Docker image that can be extended for running Elastic -# Stack images. +# Create a basic filesystem that can be used to create a Docker images that +# don't require a full distro. # # Originally from: #  @@ -45,10 +45,8 @@ mknod -m 666 "$target"/dev/tty0 c 4 0 mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 -ARCH="$(arch)" - TINI_URL="" -if [[ "$ARCH" == "x86_64" ]]; then +if [[ "$platform" == "linux/amd64" ]]; then TINI_URL="https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm" fi @@ -77,7 +75,7 @@ yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ bash zip pigz \ $TINI_URL -if [[ "$ARCH" == "aarch64" ]]; then +if [[ "$platform" == "linux/arm64" ]]; then curl --retry 10 -L -o "$target"/bin/tini https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-arm64 chmod +x "$target"/bin/tini fi @@ -85,7 +83,7 @@ fi # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. Unforunately, there's no RPM for busybox available for CentOS. BUSYBOX_URL="https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox" -if [[ "$ARCH" == "aarch64" ]]; then +if [[ "$platform" == "linux/arm64" ]]; then BUSYBOX_URL="https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv8l" fi curl --retry 10 -L -o "$target"/bin/busybox "$BUSYBOX_URL" @@ -153,6 +151,6 @@ rm -rf \ "$target"/usr/bin/tini-static \ "$target"/usr/local -# Write the base filesystem to stdout. The command changes to $target, so . -# refers to that directory +# Write out the base filesystem. The command changes directory to $target, +# so '.' refers to that directory tar --numeric-owner -c -f "$output_file" -C "$target" . From 7ca75f888d5452802eae87a3c238af30af585f50 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 15 Apr 2020 12:43:22 +0100 Subject: [PATCH 10/41] More improvements --- distribution/docker/build.gradle | 11 ++- .../src/docker/bin/install-baseimage.sh | 78 ++++++++----------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index b1855cc2b7c73..f2ba7102e326e 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -27,6 +27,10 @@ dependencies { ossDockerSource project(path: ":distribution:archives:oss-linux-tar") } +private static String baseImageFilename(final String architecture) { + return "elasticsearch-baseimage-${architecture}.tar" +} + ext.expansions = { architecture, oss, local -> switch (architecture) { case "aarch64": @@ -39,7 +43,7 @@ ext.expansions = { architecture, oss, local -> final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" return [ 'platform' : "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64', - 'base_image_tar' : "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar", + 'base_image_tar' : baseImageFilename(architecture), 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, 'git_revision' : BuildParams.gitRevision, @@ -81,7 +85,7 @@ project.ext { } from("${projectDir}/build") { - include "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar" + include baseImageFilename(architecture) } } } @@ -181,11 +185,12 @@ check.dependsOn integTest void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" - def outputFile = "elasticsearch-baseimage${"aarch64".equals(architecture) ? '-aarch64' : ''}.docker.tar" + def outputFile = baseImageFilename(architecture) def outputPath = "${projectDir}/build/${outputFile}" def archArg = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { + inputs.file(installer) outputs.file(outputPath) executable 'bash' args '-c', "docker run --rm -t " + diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 7f6a338562f61..590b2632be3b9 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -27,6 +27,9 @@ if [[ -z "$output_file" ]]; then usage fi +# Start off with an up-to-date system +yum update --setopt=tsflags=nodocs -y + # Create a temporary directory into which we will install files target=$(mktemp -d --tmpdir $(basename $0).XXXXXX) @@ -45,11 +48,6 @@ mknod -m 666 "$target"/dev/tty0 c 4 0 mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 -TINI_URL="" -if [[ "$platform" == "linux/amd64" ]]; then - TINI_URL="https://github.com/krallin/tini/releases/download/v0.18.0/tini_0.18.0-amd64.rpm" -fi - # Install files. We attempt to install a headless Java distro, and exclude a # number of unnecessary dependencies. In so doing, we also filter out Java itself, # but since Elasticsearch ships its own JDK, with its own libs, that isn't a problem @@ -62,8 +60,10 @@ fi # # We also include some utilities that we ship with the image. # -# * `pigz` is used for compressing large heaps dumps, and is considerably faster than `gzip` for this task. -# * `tini` is a tiny but valid init for containers. This is used to cleanly control how ES and any child processes are shut down. +# * `nc` is useful for checking network issues +# * `zip` and `unzip` are for working with bundles +# * `pigz` is used for compressing large heaps dumps, and is considerably faster than `gzip` for this task. +# * `tini` is a tiny but valid init for containers. This is used to cleanly control how ES and any child processes are shut down. # yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y \ @@ -72,16 +72,14 @@ yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --skip-broken \ install \ java-latest-openjdk-headless \ - bash zip pigz \ - $TINI_URL + bash nc zip upzip pigz -if [[ "$platform" == "linux/arm64" ]]; then - curl --retry 10 -L -o "$target"/bin/tini https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-arm64 - chmod +x "$target"/bin/tini -fi +curl --retry 10 -L -o "$target"/bin/tini \ + "https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-$(basename $platform)" +chmod +x "$target"/bin/tini # Use busybox instead of installing more RPMs, which can pull in all kinds of -# stuff we don't want. Unforunately, there's no RPM for busybox available for CentOS. +# stuff we don't want. There's no RPM for busybox available for CentOS. BUSYBOX_URL="https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox" if [[ "$platform" == "linux/arm64" ]]; then BUSYBOX_URL="https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv8l" @@ -107,49 +105,41 @@ tar cf - /etc/pki | (cd "$target" && tar xf -) yum --installroot="$target" -y clean all -# effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target". -# locales -rm -rf "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} -# docs and man pages -rm -rf "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons} -# cracklib -rm -rf "$target"/usr/share/cracklib -# i18n -rm -rf "$target"/usr/share/i18n -# yum cache -rm -rf "$target"/var/cache/yum -# sln -rm -rf "$target"/sbin/sln -# ldconfig -rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig -mkdir -p --mode=0755 "$target"/var/cache/ldconfig - -# Remove a bunch of other stuff that isn't required rm -rf \ - "$target"/etc/yum* \ - "$target"/etc/csh* \ + "$target"/etc/X11 \ "$target"/etc/centos-release* \ + "$target"/etc/csh* \ + "$target"/etc/groff \ "$target"/etc/profile* \ "$target"/etc/skel* \ - "$target"/etc/X11 \ - "$target"/usr/share/awk \ - "$target"/usr/lib/systemd \ + "$target"/etc/yum* \ + "$target"/sbin/sln \ + "$target"/usr/bin/rpm \ + "$target"/usr/bin/tini-static \ "$target"/usr/lib/dracut \ - "$target"/var/log/yum.log \ - "$target"/var/lib/yum \ - "$target"/var/lib/rpm \ + "$target"/usr/lib/systemd \ "$target"/usr/lib/udev \ + "${target}/usr/local" \ + "$target"/usr/share/awk \ "$target"/usr/share/centos-release \ + "$target"/usr/share/cracklib \ "$target"/usr/share/desktop-directories \ "$target"/usr/share/gcc-* \ + "$target"/usr/share/i18n \ "$target"/usr/share/icons \ "$target"/usr/share/licenses \ "$target"/usr/share/xsessions \ "$target"/usr/share/zoneinfo \ - "$target"/etc/groff \ - "$target"/usr/bin/rpm \ - "$target"/usr/bin/tini-static \ - "$target"/usr/local + "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons} \ + "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} \ + "$target"/var/cache/yum \ + "$target"/var/lib/rpm \ + "$target"/var/lib/yum \ + "$target"/var/log/yum.log + +# ldconfig +rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig +mkdir -p --mode=0755 "$target"/var/cache/ldconfig # Write out the base filesystem. The command changes directory to $target, # so '.' refers to that directory From c5d9aea52b9a66eac8cc517819a887cef1227627 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 15 Apr 2020 14:49:25 +0100 Subject: [PATCH 11/41] Build a static curl binary during the Docker build --- distribution/docker/build.gradle | 34 ++++++++-- .../docker/src/docker/bin/build-curl.sh | 68 +++++++++++++++++++ .../src/docker/bin/install-baseimage.sh | 9 ++- 3 files changed, 101 insertions(+), 10 deletions(-) create mode 100755 distribution/docker/src/docker/bin/build-curl.sh diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index f2ba7102e326e..c9d74729ce9a6 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -183,20 +183,43 @@ task integTest(type: Test) { check.dependsOn integTest +void addBuildStaticCurl(final String architecture) { + def installer = "${projectDir}/src/docker/bin/build-curl.sh" + def workDir = "${projectDir}/build/static-curl-${architecture}" + def outputPath = "${workDir}/build/bin/curl" + + final Task buildDockerBaseImageTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { + inputs.file(installer) + outputs.file(outputPath) + executable 'docker' + args 'run', '--rm', '-t', + '-v', "${workDir}:/work", + '-v', "${installer}:/build-curl.sh", + 'centos:7', '/build-curl.sh' + } + + buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } +} + void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${projectDir}/build/${outputFile}" def archArg = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' + def curlPath = "${projectDir}/build/static-curl-${architecture}/build/bin/curl" final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { + TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) + dependsOn(buildStaticCurlTask) + inputs.file(installer) outputs.file(outputPath) - executable 'bash' - args '-c', "docker run --rm -t " + - "-v ${projectDir}/build:/build " + - "-v ${installer}:/tmp/install-baseimage.sh " + - "centos:7 /tmp/install-baseimage.sh ${archArg} /build/${outputFile}" + executable 'docker' + args 'run', '--rm', '-t', + '-v', "${projectDir}/build:/build", + '-v', "${installer}:/tmp/install-baseimage.sh", + '-v', "${curlPath}:/curl", + 'centos:7', '/tmp/install-baseimage.sh', archArg, "/build/${outputFile}" } buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } @@ -228,6 +251,7 @@ void addBuildDockerImage(final String architecture, final boolean oss) { } for (final String architecture : ["aarch64", "x64"]) { + addBuildStaticCurl(architecture) for (final boolean oss : [false, true]) { addBuildDockerBaseImage(architecture, oss) addCopyDockerContextTask(architecture, oss) diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh new file mode 100755 index 0000000000000..dbff21908808d --- /dev/null +++ b/distribution/docker/src/docker/bin/build-curl.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -ex + +cd /work + +# 1. Download everything up-front, since there's no point continuing if we +# can't fetch everything. +curl -o zlib-1.2.11.tar.gz https://www.zlib.net/zlib-1.2.11.tar.gz +curl -o openssl-1.1.1f.tar.gz https://www.openssl.org/source/openssl-1.1.1f.tar.gz +curl -o openssl-1.1.1f.tar.gz.sha256 https://www.openssl.org/source/openssl-1.1.1f.tar.gz.sha256 +curl -o curl-7.69.1.tar.gz https://curl.haxx.se/download/curl-7.69.1.tar.gz + +export LDFLAGS=-static + +# The OpenSSL checksum doesn't include the filename, so we can't use `sha256sum -c` +sha256sum openssl-1.1.1f.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1f.tar.gz.sha256 + +tar xf zlib-1.2.11.tar.gz +tar xf openssl-1.1.1f.tar.gz +tar xf curl-7.69.1.tar.gz + +yum update -y +yum install -y \ + file \ + gcc \ + glibc-static \ + make \ + perl + +# Statically build libz +cd /work/zlib-1.2.11 +./configure --static +make +make install + +# Statically build openssl +cd /work/openssl-1.1.1f +./config -v no-shared +make +make install + +# Statically build curl +cd /work/curl-7.69.1 +./configure \ + --with-ssl=/usr/local \ + --disable-shared \ + --enable-static \ + --prefix=/work/build \ + --disable-ldap \ + --disable-sspi \ + --without-librtmp \ + --disable-ftp \ + --disable-file \ + --disable-dict \ + --disable-telnet \ + --disable-tftp \ + --disable-rtsp \ + --disable-pop3 \ + --disable-imap \ + --disable-smtp \ + --disable-gopher \ + --disable-smb \ + --without-libidn \ + --disable-ares +make +make install +strip /work/build/bin/curl diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 590b2632be3b9..4c455b2f7319a 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -94,11 +94,10 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# Copy in our mostly-static curl build -# TODO -# cp /build/curl "$target"/usr/bin/curl +# Copy in our mostly-static curl build, that we bind-mounted in +cp /curl "$target"/usr/bin/curl -# Curl needs files under here. More importantly, e.g. we change Elasticsearch's +# Curl needs files under here. More importantly, we change Elasticsearch's # bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of # the bundled cacerts. tar cf - /etc/pki | (cd "$target" && tar xf -) @@ -137,7 +136,7 @@ rm -rf \ "$target"/var/lib/yum \ "$target"/var/log/yum.log -# ldconfig +# ldconfig rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig mkdir -p --mode=0755 "$target"/var/cache/ldconfig From b9f652f13c207f0efd82efbdcc7d601ee9876dae Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 15 Apr 2020 16:03:10 +0100 Subject: [PATCH 12/41] Trying to get cross-arch Docker builds to work --- .../elasticsearch/gradle/docker/DockerBuildTask.java | 4 +++- distribution/docker/build.gradle | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java b/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java index 9bc926ecf8ae7..7777f39aadb11 100644 --- a/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java @@ -1,5 +1,6 @@ package org.elasticsearch.gradle.docker; +import org.elasticsearch.gradle.Architecture; import org.elasticsearch.gradle.LoggedExec; import org.gradle.api.DefaultTask; import org.gradle.api.file.DirectoryProperty; @@ -33,7 +34,8 @@ public class DockerBuildTask extends DefaultTask { @Inject public DockerBuildTask(WorkerExecutor workerExecutor) { this.workerExecutor = workerExecutor; - this.markerFile.set(getProject().getLayout().getBuildDirectory().file("markers/" + this.getName() + ".marker")); + final String markerPath = "markers/" + this.getName() + "-" + Architecture.current().toString().toLowerCase() + ".marker"; + this.markerFile.set(getProject().getLayout().getBuildDirectory().file(markerPath)); } @TaskAction diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index c9d74729ce9a6..a87ca06a459cc 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -187,6 +187,7 @@ void addBuildStaticCurl(final String architecture) { def installer = "${projectDir}/src/docker/bin/build-curl.sh" def workDir = "${projectDir}/build/static-curl-${architecture}" def outputPath = "${workDir}/build/bin/curl" + def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' final Task buildDockerBaseImageTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { inputs.file(installer) @@ -195,7 +196,7 @@ void addBuildStaticCurl(final String architecture) { args 'run', '--rm', '-t', '-v', "${workDir}:/work", '-v', "${installer}:/build-curl.sh", - 'centos:7', '/build-curl.sh' + image, '/build-curl.sh' } buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } @@ -205,7 +206,8 @@ void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${projectDir}/build/${outputFile}" - def archArg = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' + def platform = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' + def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' def curlPath = "${projectDir}/build/static-curl-${architecture}/build/bin/curl" final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { @@ -219,7 +221,7 @@ void addBuildDockerBaseImage(final String architecture, final boolean oss) { '-v', "${projectDir}/build:/build", '-v', "${installer}:/tmp/install-baseimage.sh", '-v', "${curlPath}:/curl", - 'centos:7', '/tmp/install-baseimage.sh', archArg, "/build/${outputFile}" + image, '/tmp/install-baseimage.sh', platform, "/build/${outputFile}" } buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } @@ -246,7 +248,9 @@ void addBuildDockerImage(final String architecture, final boolean oss) { ] } } - buildDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } + buildDockerImageTask.onlyIf { + Architecture.current().name().toLowerCase().equals(architecture) + } assemble.dependsOn(buildDockerImageTask) } From 0cfa2cc9e473d9506e70e7ff87b79e878fe0a8ef Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 15 Apr 2020 21:01:34 +0100 Subject: [PATCH 13/41] Don't provide curl, and suggest wget instead --- distribution/docker/build.gradle | 37 +++------- distribution/docker/src/docker/Dockerfile | 19 ++++-- .../docker/src/docker/bin/build-curl.sh | 68 ------------------- distribution/docker/src/docker/bin/curl | 14 ++++ .../src/docker/bin/install-baseimage.sh | 8 +-- 5 files changed, 36 insertions(+), 110 deletions(-) delete mode 100755 distribution/docker/src/docker/bin/build-curl.sh create mode 100755 distribution/docker/src/docker/bin/curl diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index a87ca06a459cc..d7568597c0372 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -39,17 +39,20 @@ ext.expansions = { architecture, oss, local -> default: throw new IllegalArgumentException("unrecongized architecture [" + architecture + "], must be one of (aarch64|x64)") } - final String classifier = "aarch64".equals(architecture) ? "linux-aarch64" : "linux-x86_64" - final String elasticsearch = oss ? "elasticsearch-oss-${VersionProperties.elasticsearch}-${classifier}.tar.gz" : "elasticsearch-${VersionProperties.elasticsearch}-${classifier}.tar.gz" + + final boolean isAarch64 = "aarch64".equals(architecture) + final String classifier = isAarch64 ? "linux-aarch64" : "linux-x86_64" + final String elasticsearch = "elasticsearch${oss ? '-oss' : ''}-${VersionProperties.elasticsearch}-${classifier}.tar.gz" + return [ - 'platform' : "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64', + 'platform' : isAarch64 ? 'linux/arm64' : 'linux/amd64', + 'builder_image' : isAarch64 ? 'arm64v8/centos:7' : 'centos:7', 'base_image_tar' : baseImageFilename(architecture), 'build_date' : BuildParams.buildDate, 'elasticsearch' : elasticsearch, 'git_revision' : BuildParams.gitRevision, 'license' : oss ? 'Apache-2.0' : 'Elastic-License', 'source_elasticsearch': local ? "COPY $elasticsearch /opt/" : "RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/elasticsearch/${elasticsearch} && cd -", - 'tini_suffix' : "aarch64".equals(architecture) ? "-arm64" : "", 'version' : VersionProperties.elasticsearch ] } @@ -183,44 +186,21 @@ task integTest(type: Test) { check.dependsOn integTest -void addBuildStaticCurl(final String architecture) { - def installer = "${projectDir}/src/docker/bin/build-curl.sh" - def workDir = "${projectDir}/build/static-curl-${architecture}" - def outputPath = "${workDir}/build/bin/curl" - def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' - - final Task buildDockerBaseImageTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { - inputs.file(installer) - outputs.file(outputPath) - executable 'docker' - args 'run', '--rm', '-t', - '-v', "${workDir}:/work", - '-v', "${installer}:/build-curl.sh", - image, '/build-curl.sh' - } - - buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } -} - void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${projectDir}/build/${outputFile}" def platform = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' - def curlPath = "${projectDir}/build/static-curl-${architecture}/build/bin/curl" final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { - TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) - dependsOn(buildStaticCurlTask) - inputs.file(installer) outputs.file(outputPath) + outputs.cacheIf({ true }) executable 'docker' args 'run', '--rm', '-t', '-v', "${projectDir}/build:/build", '-v', "${installer}:/tmp/install-baseimage.sh", - '-v', "${curlPath}:/curl", image, '/tmp/install-baseimage.sh', platform, "/build/${outputFile}" } @@ -255,7 +235,6 @@ void addBuildDockerImage(final String architecture, final boolean oss) { } for (final String architecture : ["aarch64", "x64"]) { - addBuildStaticCurl(architecture) for (final boolean oss : [false, true]) { addBuildDockerBaseImage(architecture, oss) addCopyDockerContextTask(architecture, oss) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index c68ac04d7c238..6dd72889486e2 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -11,14 +11,15 @@ # Set gid=0 and make group perms==owner perms ################################################################################ -FROM --platform=${platform} scratch AS builder - -ADD ${base_image_tar} / +# We could use `scratch` and `ADD ${base_image_tar} /` like we do for the +# final image, but we need `curl` if the source line is fetching a remote +# artifact, and that isn't include in the final image. +FROM ${builder_image} AS builder ENV PATH /usr/share/elasticsearch/bin:\$PATH -RUN addgroup -g 1000 elasticsearch && \ - adduser -D -u 1000 -G elasticsearch -h /usr/share/elasticsearch elasticsearch +RUN groupadd -g 1000 elasticsearch && \ + adduser -u 1000 -g 1000 -d /usr/share/elasticsearch elasticsearch WORKDIR /usr/share/elasticsearch @@ -39,10 +40,13 @@ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties FROM --platform=${platform} scratch +# Setup the initial filesystem. ADD ${base_image_tar} / ENV ELASTIC_CONTAINER true +# Most of the utilities are provided with Busybox, hence the different user +# management commands here compared to the builder image. RUN addgroup -g 1000 elasticsearch && \ adduser -D -u 1000 -G elasticsearch -h /usr/share/elasticsearch elasticsearch && \ addgroup elasticsearch root && \ @@ -60,8 +64,9 @@ RUN ln -sf /etc/pki/ca-trust/extracted/java/cacerts /usr/share/elasticsearch/jdk ENV PATH /usr/share/elasticsearch/bin:\$PATH COPY bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY bin/curl /usr/bin/curl -# 1. Sync the user and group of /etc/passwd +# 1. Sync the user and group permissions of /etc/passwd # 2. Set correct permissions of the entrypoint # 3. Ensure that there are no files with setuid or setgid, in order to mitigate "stackclash" attacks. RUN chmod g=u /etc/passwd && \ @@ -93,7 +98,7 @@ LABEL org.label-schema.build-date="${build_date}" \ USER elasticsearch:root # Our actual entrypoint is `tini`, a minimal but functional init program. It -# calls the actual entrypoint for us, and correctly forwards signals. +# calls the entrypoint we provide, while correctly forwarding signals. ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] # Dummy overridable parameter parsed by entrypoint diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh deleted file mode 100755 index dbff21908808d..0000000000000 --- a/distribution/docker/src/docker/bin/build-curl.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -set -ex - -cd /work - -# 1. Download everything up-front, since there's no point continuing if we -# can't fetch everything. -curl -o zlib-1.2.11.tar.gz https://www.zlib.net/zlib-1.2.11.tar.gz -curl -o openssl-1.1.1f.tar.gz https://www.openssl.org/source/openssl-1.1.1f.tar.gz -curl -o openssl-1.1.1f.tar.gz.sha256 https://www.openssl.org/source/openssl-1.1.1f.tar.gz.sha256 -curl -o curl-7.69.1.tar.gz https://curl.haxx.se/download/curl-7.69.1.tar.gz - -export LDFLAGS=-static - -# The OpenSSL checksum doesn't include the filename, so we can't use `sha256sum -c` -sha256sum openssl-1.1.1f.tar.gz | cut -f1 -d" " | diff - openssl-1.1.1f.tar.gz.sha256 - -tar xf zlib-1.2.11.tar.gz -tar xf openssl-1.1.1f.tar.gz -tar xf curl-7.69.1.tar.gz - -yum update -y -yum install -y \ - file \ - gcc \ - glibc-static \ - make \ - perl - -# Statically build libz -cd /work/zlib-1.2.11 -./configure --static -make -make install - -# Statically build openssl -cd /work/openssl-1.1.1f -./config -v no-shared -make -make install - -# Statically build curl -cd /work/curl-7.69.1 -./configure \ - --with-ssl=/usr/local \ - --disable-shared \ - --enable-static \ - --prefix=/work/build \ - --disable-ldap \ - --disable-sspi \ - --without-librtmp \ - --disable-ftp \ - --disable-file \ - --disable-dict \ - --disable-telnet \ - --disable-tftp \ - --disable-rtsp \ - --disable-pop3 \ - --disable-imap \ - --disable-smtp \ - --disable-gopher \ - --disable-smb \ - --without-libidn \ - --disable-ares -make -make install -strip /work/build/bin/curl diff --git a/distribution/docker/src/docker/bin/curl b/distribution/docker/src/docker/bin/curl new file mode 100755 index 0000000000000..41bfea98d48c9 --- /dev/null +++ b/distribution/docker/src/docker/bin/curl @@ -0,0 +1,14 @@ +#!/bin/sh + +cat <<'EOF' + +'curl' is not provided in this image. You can use 'wget' to fetch resources. + +If you need to provide HTTP basic authentication, you can do the following: + + AUTH="$(echo username:password | base64)" + wget --header "Authentication: Basic $AUTH" http://localhost:9200/ + +EOF + +exit 1 diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 4c455b2f7319a..0897fa67d8a84 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -94,12 +94,8 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# Copy in our mostly-static curl build, that we bind-mounted in -cp /curl "$target"/usr/bin/curl - -# Curl needs files under here. More importantly, we change Elasticsearch's -# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of -# the bundled cacerts. +# We change Elasticsearch's bundled JDK to use +# /etc/pki/ca-trust/extracted/java/cacerts instead of the bundled cacerts. tar cf - /etc/pki | (cd "$target" && tar xf -) yum --installroot="$target" -y clean all From 8a54c4cc16dd50d2189f322889602b0f4fabab95 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 16 Apr 2020 10:20:38 +0100 Subject: [PATCH 14/41] Revert something --- .../java/org/elasticsearch/gradle/docker/DockerBuildTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java b/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java index 7777f39aadb11..443014ec5012f 100644 --- a/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/docker/DockerBuildTask.java @@ -34,7 +34,7 @@ public class DockerBuildTask extends DefaultTask { @Inject public DockerBuildTask(WorkerExecutor workerExecutor) { this.workerExecutor = workerExecutor; - final String markerPath = "markers/" + this.getName() + "-" + Architecture.current().toString().toLowerCase() + ".marker"; + final String markerPath = "markers/" + this.getName() + ".marker"; this.markerFile.set(getProject().getLayout().getBuildDirectory().file(markerPath)); } From f90d9d5aaf714a5f0651d62bbd6c87ec1009f87a Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 16 Apr 2020 10:28:16 +0100 Subject: [PATCH 15/41] Tweaks --- distribution/docker/build.gradle | 4 +--- .../java/org/elasticsearch/packaging/util/Docker.java | 11 ++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 630d72dde1086..a9a820a81acd3 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -231,9 +231,7 @@ void addBuildDockerImage(final String architecture, final boolean oss) { ] } } - buildDockerImageTask.onlyIf { - Architecture.current().name().toLowerCase().equals(architecture) - } + buildDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } assemble.dependsOn(buildDockerImageTask) } diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index 35cc48e79db35..e86ed9b5168ea 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -490,13 +490,14 @@ private static void verifyOssInstallation(Installation es) { Stream.of("LICENSE.txt", "NOTICE.txt", "README.asciidoc").forEach(doc -> assertPermissionsAndOwnership(es.home.resolve(doc), p644)); + // nc is useful for checking network issues // zip/unzip are installed to help users who are working with certificates. - // pigz is useful for compressing large heapdumps more quickly than gzip. - Stream.of("zip", "unzip", "pigz") + // pigz is useful for compressing large heap dumps more quickly than gzip. + Stream.of("zip", "unzip", "pigz", "nc") .forEach( - cliPackage -> assertTrue( - cliPackage + " ought to be installed. ", - dockerShell.runIgnoreExitCode("which " + cliPackage).isSuccess() + cliBinary -> assertTrue( + cliBinary + " ought to be installed. ", + dockerShell.runIgnoreExitCode("which " + cliBinary).isSuccess() ) ); } From 91557a52967334c3594e31f1852e952d59fe03d8 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 16 Apr 2020 14:26:20 +0100 Subject: [PATCH 16/41] Add missing license and javadoc --- .../packaging/util/ProcessInfo.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java index 8c526aa47dd8f..23ba400af2f11 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java @@ -1,3 +1,22 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.elasticsearch.packaging.util; import java.util.List; @@ -6,6 +25,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; +/** + * Encapsulates the fetching of information about a running process. + * + * This is helpful on stripped-down images where, in order to fetch full information about a process, + * we have to consult /proc. + */ public class ProcessInfo { public final int pid; public final int uid; @@ -21,6 +46,10 @@ public ProcessInfo(int pid, int uid, int gid, String username, String group) { this.group = group; } + /** + * Fetches process information about command, using sh to execute commands. + * @return a populated ProcessInfo object + */ public static ProcessInfo getProcessInfo(Shell sh, String command) { final List processes = sh.run("pgrep " + command).stdout.lines().collect(Collectors.toList()); From 6b40ac52ed5aa0096c8827260891ad9f9794ea7e Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 16 Apr 2020 16:06:22 +0100 Subject: [PATCH 17/41] Use wget instead of curl for docker-compose heathchecks --- distribution/docker/docker-compose.yml | 10 ++++++---- qa/remote-clusters/docker-compose-oss.yml | 4 ++-- qa/remote-clusters/docker-compose.yml | 6 ++++-- qa/wildfly/docker-compose-oss.yml | 2 +- qa/wildfly/docker-compose.yml | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/distribution/docker/docker-compose.yml b/distribution/docker/docker-compose.yml index 08e39ff8dd750..81e12156aace7 100644 --- a/distribution/docker/docker-compose.yml +++ b/distribution/docker/docker-compose.yml @@ -46,7 +46,8 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] + # The base64-encoded value below is x_pack_rest_user:x-pack-test-password + test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -95,7 +96,8 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] + # The base64-encoded value below is x_pack_rest_user:x-pack-test-password + test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -129,7 +131,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -160,7 +162,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/remote-clusters/docker-compose-oss.yml b/qa/remote-clusters/docker-compose-oss.yml index 5cd891373c5ee..378f7d3fe24f4 100644 --- a/qa/remote-clusters/docker-compose-oss.yml +++ b/qa/remote-clusters/docker-compose-oss.yml @@ -32,7 +32,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -64,7 +64,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/remote-clusters/docker-compose.yml b/qa/remote-clusters/docker-compose.yml index 05af5541785ca..f92655b40e25d 100644 --- a/qa/remote-clusters/docker-compose.yml +++ b/qa/remote-clusters/docker-compose.yml @@ -47,7 +47,8 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] + # The base64-encoded value below is x_pack_rest_user:x-pack-test-password + test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -97,7 +98,8 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] + # The base64-encoded value below is x_pack_rest_user:x-pack-test-password + test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/wildfly/docker-compose-oss.yml b/qa/wildfly/docker-compose-oss.yml index e328240a367ad..1589819b78b84 100644 --- a/qa/wildfly/docker-compose-oss.yml +++ b/qa/wildfly/docker-compose-oss.yml @@ -29,7 +29,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-k", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/wildfly/docker-compose.yml b/qa/wildfly/docker-compose.yml index 005bb5f0c7b34..4334c419c976c 100644 --- a/qa/wildfly/docker-compose.yml +++ b/qa/wildfly/docker-compose.yml @@ -29,7 +29,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "curl", "-f", "-k", "http://localhost:9200"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 From c3815602d494e74a187ae43832d0ae1e9c19f179 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 10:57:30 +0100 Subject: [PATCH 18/41] Address review feedback --- distribution/docker/build.gradle | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index a9a820a81acd3..3fe06d7b58ba5 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -192,17 +192,21 @@ check.dependsOn integTest void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) - def outputPath = "${projectDir}/build/${outputFile}" + def outputPath = "${buildDir}/${outputFile}" def platform = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { - inputs.file(installer) + inputs.file(installer).withPropertyName('base filesystem builder').withPathSensitivity(PathSensitivity.RELATIVE) + inputs.properties([ + 'image': image, + 'platform': platform, + ]) outputs.file(outputPath) outputs.cacheIf({ true }) executable 'docker' args 'run', '--rm', '-t', - '-v', "${projectDir}/build:/build", + '-v', "${buildDir}:/build", '-v', "${installer}:/tmp/install-baseimage.sh", image, '/tmp/install-baseimage.sh', platform, "/build/${outputFile}" } From 426b422c7c0f80d5a72af0bdf595f73e378c2cfb Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 11:13:41 +0100 Subject: [PATCH 19/41] Compress base filesystem tar with gzip --- distribution/docker/build.gradle | 2 +- distribution/docker/src/docker/bin/install-baseimage.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 3fe06d7b58ba5..4d6ba7ecfb885 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -28,7 +28,7 @@ dependencies { } private static String baseImageFilename(final String architecture) { - return "elasticsearch-baseimage-${architecture}.tar" + return "elasticsearch-baseimage-${architecture}.tar.gz" } ext.expansions = { architecture, oss, local -> diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 0897fa67d8a84..202180f8877a6 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -136,6 +136,6 @@ rm -rf \ rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig mkdir -p --mode=0755 "$target"/var/cache/ldconfig -# Write out the base filesystem. The command changes directory to $target, +# Write out the base filesystem. The -C option changes directory to $target, # so '.' refers to that directory -tar --numeric-owner -c -f "$output_file" -C "$target" . +tar czf "$output_file" --numeric-owner -C "$target" . From e54e83c4a961de801692fafa93d41a4995d7b5ee Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 14:50:49 +0100 Subject: [PATCH 20/41] Fix typo that stopped :qa:remote-clusters from logging --- qa/remote-clusters/docker-test-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/remote-clusters/docker-test-entrypoint.sh b/qa/remote-clusters/docker-test-entrypoint.sh index 1dca4b6a35e73..f1b83a56c598b 100755 --- a/qa/remote-clusters/docker-test-entrypoint.sh +++ b/qa/remote-clusters/docker-test-entrypoint.sh @@ -4,4 +4,4 @@ cd /usr/share/elasticsearch/bin/ echo "testnode" > /tmp/password cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.transport.ssl.keystore.secure_password' cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.http.ssl.keystore.secure_password' -/usr/local/bin/docker-entrypoint.sh | tee > /usr/share/elasticsearch/logs/console.log +/usr/local/bin/docker-entrypoint.sh | tee /usr/share/elasticsearch/logs/console.log From d826cdf8ac6e4041da4ad17112d9987f2b29d250 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 17:29:30 +0100 Subject: [PATCH 21/41] Fix docker-compose healthchecks --- distribution/docker/docker-compose.yml | 10 ++++------ distribution/docker/docker-test-entrypoint.sh | 2 +- qa/remote-clusters/docker-compose-oss.yml | 4 ++-- qa/remote-clusters/docker-compose.yml | 6 ++---- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/distribution/docker/docker-compose.yml b/distribution/docker/docker-compose.yml index 81e12156aace7..8597ee7de3c09 100644 --- a/distribution/docker/docker-compose.yml +++ b/distribution/docker/docker-compose.yml @@ -46,8 +46,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # The base64-encoded value below is x_pack_rest_user:x-pack-test-password - test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 @@ -96,8 +95,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # The base64-encoded value below is x_pack_rest_user:x-pack-test-password - test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 @@ -131,7 +129,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 @@ -162,7 +160,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 diff --git a/distribution/docker/docker-test-entrypoint.sh b/distribution/docker/docker-test-entrypoint.sh index 1dca4b6a35e73..f1b83a56c598b 100755 --- a/distribution/docker/docker-test-entrypoint.sh +++ b/distribution/docker/docker-test-entrypoint.sh @@ -4,4 +4,4 @@ cd /usr/share/elasticsearch/bin/ echo "testnode" > /tmp/password cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.transport.ssl.keystore.secure_password' cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.http.ssl.keystore.secure_password' -/usr/local/bin/docker-entrypoint.sh | tee > /usr/share/elasticsearch/logs/console.log +/usr/local/bin/docker-entrypoint.sh | tee /usr/share/elasticsearch/logs/console.log diff --git a/qa/remote-clusters/docker-compose-oss.yml b/qa/remote-clusters/docker-compose-oss.yml index 378f7d3fe24f4..1a2d862c70df6 100644 --- a/qa/remote-clusters/docker-compose-oss.yml +++ b/qa/remote-clusters/docker-compose-oss.yml @@ -32,7 +32,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 @@ -64,7 +64,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/remote-clusters/docker-compose.yml b/qa/remote-clusters/docker-compose.yml index f92655b40e25d..fca50d5dfb0ae 100644 --- a/qa/remote-clusters/docker-compose.yml +++ b/qa/remote-clusters/docker-compose.yml @@ -47,8 +47,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # The base64-encoded value below is x_pack_rest_user:x-pack-test-password - test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 @@ -98,8 +97,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # The base64-encoded value below is x_pack_rest_user:x-pack-test-password - test: ["CMD", "wget", '--header', 'Authentication: Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZAo=', "-q", "https://localhost:9200"] + test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s retries: 5 From 2f2348224dfd352fb4028099e1e441f25ba826d3 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 21:05:44 +0100 Subject: [PATCH 22/41] docker-compose fixes --- distribution/docker/docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/distribution/docker/docker-compose.yml b/distribution/docker/docker-compose.yml index 8597ee7de3c09..c2a634c98b083 100644 --- a/distribution/docker/docker-compose.yml +++ b/distribution/docker/docker-compose.yml @@ -46,6 +46,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s + # BusyBox's wget implementation struggles with SSL, so tail the log instead test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s @@ -95,6 +96,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s + # BusyBox's wget implementation struggles with SSL, so tail the log instead test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] interval: 10s timeout: 2s @@ -129,7 +131,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -160,7 +162,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "wget", "-q", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 From 56340da276eece963bd79398d5c0adaece3191d6 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Mon, 20 Apr 2020 21:18:25 +0100 Subject: [PATCH 23/41] Remove curl wrapper around wget --- distribution/docker/src/docker/Dockerfile | 1 - distribution/docker/src/docker/bin/curl | 14 -------------- 2 files changed, 15 deletions(-) delete mode 100755 distribution/docker/src/docker/bin/curl diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 6dd72889486e2..6151661af1b74 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -64,7 +64,6 @@ RUN ln -sf /etc/pki/ca-trust/extracted/java/cacerts /usr/share/elasticsearch/jdk ENV PATH /usr/share/elasticsearch/bin:\$PATH COPY bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -COPY bin/curl /usr/bin/curl # 1. Sync the user and group permissions of /etc/passwd # 2. Set correct permissions of the entrypoint diff --git a/distribution/docker/src/docker/bin/curl b/distribution/docker/src/docker/bin/curl deleted file mode 100755 index 41bfea98d48c9..0000000000000 --- a/distribution/docker/src/docker/bin/curl +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -cat <<'EOF' - -'curl' is not provided in this image. You can use 'wget' to fetch resources. - -If you need to provide HTTP basic authentication, you can do the following: - - AUTH="$(echo username:password | base64)" - wget --header "Authentication: Basic $AUTH" http://localhost:9200/ - -EOF - -exit 1 From 18b012835582be461c90762d0588694e49d94a20 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 21 Apr 2020 11:56:20 +0100 Subject: [PATCH 24/41] Set GCOS in /etc/password for Docker --- distribution/docker/src/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 6151661af1b74..05e3c09c2b0f3 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -48,7 +48,7 @@ ENV ELASTIC_CONTAINER true # Most of the utilities are provided with Busybox, hence the different user # management commands here compared to the builder image. RUN addgroup -g 1000 elasticsearch && \ - adduser -D -u 1000 -G elasticsearch -h /usr/share/elasticsearch elasticsearch && \ + adduser -D -u 1000 -G elasticsearch -g elasticsearch -h /usr/share/elasticsearch elasticsearch && \ addgroup elasticsearch root && \ chmod 0775 /usr/share/elasticsearch && \ chgrp 0 /usr/share/elasticsearch From 22d0035bbd743b5fa575500bf36d28c4eb7a4d48 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 21 Apr 2020 11:56:43 +0100 Subject: [PATCH 25/41] Add a note about extending the shrunk Docker image --- docs/reference/setup/install/docker.asciidoc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/reference/setup/install/docker.asciidoc b/docs/reference/setup/install/docker.asciidoc index 2bd5eef65dc7c..cfe869e22e2ea 100644 --- a/docs/reference/setup/install/docker.asciidoc +++ b/docs/reference/setup/install/docker.asciidoc @@ -412,4 +412,22 @@ You must explicitly accept them either by: See {plugins}/_other_command_line_parameters.html[Plugin management] for more information. +The {es} Docker image tries to include only what is required to run {es}, and therefore does not provide a +package manager. It is possible to add additional utilities with a multi-phase Docker build. You must also +copy any dependencies, for example shared libraries. + +[source,sh,subs="attributes"] +-------------------------------------------- +FROM centos:7 AS builder +yum install -y some-package + +FROM docker.elastic.co/elasticsearch/elasticsearch:{version} +COPY --from=builder /usr/bin/some-utility /usr/bin/ +COPY --from=builder /usr/lib/some-lib.so /usr/lib/ +-------------------------------------------- + +You should use `centos:7` as a base, in order to avoid incompatibilities, and you can use +http://man7.org/linux/man-pages/man1/ldd.1.html[`ldd`] to list the shared libraries required +by a utility. + include::next-steps.asciidoc[] From c8a82a7f4b913a5a4d72b8a04561a9ab1ad1f7df Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 22 Apr 2020 15:42:59 +0100 Subject: [PATCH 26/41] Reintroduce a static curl build, only much faster --- distribution/docker/build.gradle | 36 ++++++++- distribution/docker/docker-compose.yml | 10 +-- .../docker/src/docker/bin/build-curl.sh | 78 +++++++++++++++++++ .../src/docker/bin/install-baseimage.sh | 8 +- qa/remote-clusters/docker-compose-oss.yml | 4 +- qa/remote-clusters/docker-compose.yml | 4 +- qa/wildfly/docker-compose-oss.yml | 2 +- qa/wildfly/docker-compose.yml | 2 +- 8 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 distribution/docker/src/docker/bin/build-curl.sh diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 4d6ba7ecfb885..2bcea88d7eef1 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -189,25 +189,52 @@ task integTest(type: Test) { check.dependsOn integTest +void addBuildStaticCurl(final String architecture) { + def installer = "${projectDir}/src/docker/bin/build-curl.sh" + def workDir = "${buildDir}/static-curl-${architecture}" + def outputPath = "${workDir}/curl" + // We build on Alpine as it's easier to create a static executable. The + // result runs just find on CentOS. + def image = ("aarch64".equals(architecture) ? 'arm64v8/' : '') + 'alpine:latest' + + final Task buildStaticCurlTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { + inputs.file(installer).withPropertyName('static curl binary builder').withPathSensitivity(PathSensitivity.RELATIVE) + inputs.property('architecture', architecture) + outputs.file(outputPath) + outputs.cacheIf({ true }) + + executable 'docker' + args 'run', '--rm', '-t', + '-v', "${workDir}:/work", + '-v', "${installer}:/build-curl.sh", + image, '/build-curl.sh' + } + + buildStaticCurlTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } +} + void addBuildDockerBaseImage(final String architecture, final boolean oss) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${buildDir}/${outputFile}" def platform = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' + def curlPath = "${buildDir}/static-curl-${architecture}/curl" final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { + TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) + dependsOn(buildStaticCurlTask) + inputs.file(installer).withPropertyName('base filesystem builder').withPathSensitivity(PathSensitivity.RELATIVE) - inputs.properties([ - 'image': image, - 'platform': platform, - ]) + inputs.property('architecture', architecture) outputs.file(outputPath) outputs.cacheIf({ true }) + executable 'docker' args 'run', '--rm', '-t', '-v', "${buildDir}:/build", '-v', "${installer}:/tmp/install-baseimage.sh", + '-v', "${curlPath}:/curl", image, '/tmp/install-baseimage.sh', platform, "/build/${outputFile}" } @@ -240,6 +267,7 @@ void addBuildDockerImage(final String architecture, final boolean oss) { } for (final String architecture : ["aarch64", "x64"]) { + addBuildStaticCurl(architecture) for (final boolean oss : [false, true]) { addBuildDockerBaseImage(architecture, oss) addCopyDockerContextTask(architecture, oss) diff --git a/distribution/docker/docker-compose.yml b/distribution/docker/docker-compose.yml index c2a634c98b083..08e39ff8dd750 100644 --- a/distribution/docker/docker-compose.yml +++ b/distribution/docker/docker-compose.yml @@ -46,8 +46,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # BusyBox's wget implementation struggles with SSL, so tail the log instead - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -96,8 +95,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - # BusyBox's wget implementation struggles with SSL, so tail the log instead - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -131,7 +129,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "curl", "-f", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -162,7 +160,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "curl", "-f", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh new file mode 100644 index 0000000000000..1c89e6828340b --- /dev/null +++ b/distribution/docker/src/docker/bin/build-curl.sh @@ -0,0 +1,78 @@ +#!/bin/sh + +# Originally from: https://github.com/dtschan/curl-static/blob/ab8ccc3ff140af860065c04b1e9bcd20bbe2c2d2/build.sh +# License: https://github.com/dtschan/curl-static/blob/ab8ccc3ff1/LICENSE + +# This script must run in an Alpine Linux environment + +set -e + +VERSION=LATEST +# If you prefer a specific version you can set it specifically +# VERSION=7.66.0 +GPG_KEY_URL="https://daniel.haxx.se/mykey.asc" +GPG_KEY_PATH="/work/curl-gpg.pub" + + +# Print failure message if we exit unexpectedly +trap 'RC="$?"; echo "*** FAILED! RC=${RC}"; exit ${RC}' EXIT + +# Fetch a url to a location unless it already exists +conditional_fetch () { + local URL=$1 + local OUTPUT_PATH=$2 + if [ -e ${OUTPUT_PATH} ]; then + echo "Found existing ${OUTPUT_PATH}; reusing..." + else + echo "Fetching ${URL} to ${OUTPUT_PATH}..." + wget "${URL}" -O "${OUTPUT_PATH}" + fi +} + +# Determine tarball filename +if [ "$VERSION" = 'LATEST' ]; then + echo "Determining latest version..." + TARBALL_FILENAME=$(wget "https://curl.haxx.se/download/?C=M;O=D" -q -O- | grep -w -m 1 -o 'curl-.*\.tar\.xz"' | sed 's/"$//') +else + TARBALL_FILENAME=curl-${VERSION}.tar.xz +fi + +# Set some variables (depends on tarball filename determined above) +TARBALL_URL=https://curl.haxx.se/download/${TARBALL_FILENAME} +TARBALL_PATH=/work/${TARBALL_FILENAME} +FINAL_BIN_PATH=/work/curl + +echo "*** Fetching ${TARBALL_FILENAME} and files to validate it..." +conditional_fetch "${GPG_KEY_URL}" "${GPG_KEY_PATH}" +conditional_fetch "${TARBALL_URL}.asc" "${TARBALL_PATH}.asc" +conditional_fetch "${TARBALL_URL}" "${TARBALL_PATH}" + +echo "*** Validating source..." +apk add gnupg +gpg --import --always-trust ${GPG_KEY_PATH} +gpg --verify ${TARBALL_PATH}.asc ${TARBALL_PATH} + +echo "*** Unpacking source..." +tar xfJ ${TARBALL_PATH} +cd curl-* + +echo "*** Installing build dependencies..." +apk add gcc make musl-dev openssl-dev openssl-libs-static file + +echo "*** configuring..." +# The bundle path is set so that our `curl` can fetch an https URL on +# CentOS +./configure --disable-shared --with-ca-fallback --with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt +echo "making..." +make curl_LDFLAGS=-all-static + +echo "*** Finishing up..." +cp src/curl ${FINAL_BIN_PATH} +strip ${FINAL_BIN_PATH} +chown $(id -u):$(id -g) ${FINAL_BIN_PATH} + +# Clear the trap so when we exit there is no failure message +trap - EXIT +echo SUCCESS +ls -ld ${FINAL_BIN_PATH} +du -h ${FINAL_BIN_PATH} diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 202180f8877a6..354f79b6fa881 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -94,8 +94,12 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# We change Elasticsearch's bundled JDK to use -# /etc/pki/ca-trust/extracted/java/cacerts instead of the bundled cacerts. +# Copy in our mostly-static curl build, that we bind-mounted in +cp /curl "$target"/usr/bin/curl + +# Curl needs files under here. More importantly, we change Elasticsearch's +# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of +# the bundled cacerts. tar cf - /etc/pki | (cd "$target" && tar xf -) yum --installroot="$target" -y clean all diff --git a/qa/remote-clusters/docker-compose-oss.yml b/qa/remote-clusters/docker-compose-oss.yml index 1a2d862c70df6..5cd891373c5ee 100644 --- a/qa/remote-clusters/docker-compose-oss.yml +++ b/qa/remote-clusters/docker-compose-oss.yml @@ -32,7 +32,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -64,7 +64,7 @@ services: hard: -1 healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/remote-clusters/docker-compose.yml b/qa/remote-clusters/docker-compose.yml index fca50d5dfb0ae..05af5541785ca 100644 --- a/qa/remote-clusters/docker-compose.yml +++ b/qa/remote-clusters/docker-compose.yml @@ -47,7 +47,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 @@ -97,7 +97,7 @@ services: entrypoint: /docker-test-entrypoint.sh healthcheck: start_period: 15s - test: ["CMD", "grep", "-q", "Active license is now", "/usr/share/elasticsearch/logs/console.log"] + test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/wildfly/docker-compose-oss.yml b/qa/wildfly/docker-compose-oss.yml index 1589819b78b84..e328240a367ad 100644 --- a/qa/wildfly/docker-compose-oss.yml +++ b/qa/wildfly/docker-compose-oss.yml @@ -29,7 +29,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "curl", "-f", "-k", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 diff --git a/qa/wildfly/docker-compose.yml b/qa/wildfly/docker-compose.yml index 4334c419c976c..005bb5f0c7b34 100644 --- a/qa/wildfly/docker-compose.yml +++ b/qa/wildfly/docker-compose.yml @@ -29,7 +29,7 @@ services: hard: 65536 healthcheck: start_period: 15s - test: ["CMD", "wget", "-q", "http://localhost:9200"] + test: ["CMD", "curl", "-f", "-k", "http://localhost:9200"] interval: 10s timeout: 2s retries: 5 From 37a0e7817603a41fe21f5ebd2f77d8528df11ed0 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 22 Apr 2020 16:05:17 +0100 Subject: [PATCH 27/41] Reuse the docker base filesystem for OSS --- distribution/docker/build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 2bcea88d7eef1..f19ba0ff93d0b 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -35,7 +35,7 @@ ext.expansions = { architecture, oss, local -> switch (architecture) { case "aarch64": case "x64": - break; + break default: throw new IllegalArgumentException("unrecongized architecture [" + architecture + "], must be one of (aarch64|x64)") } @@ -96,7 +96,7 @@ project.ext { void addCopyDockerContextTask(final String architecture, final boolean oss) { task(taskName("copy", architecture, oss, "DockerContext"), type: Sync) { - TaskProvider buildBaseImageTask = tasks.named(taskName("build", architecture, oss, "DockerBaseImage")) + TaskProvider buildBaseImageTask = tasks.named(taskName("build", architecture, false, "DockerBaseImage")) dependsOn(buildBaseImageTask) expansions(architecture, oss, true).findAll { it.key != 'build_date' }.each { k, v -> @@ -213,7 +213,7 @@ void addBuildStaticCurl(final String architecture) { buildStaticCurlTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } } -void addBuildDockerBaseImage(final String architecture, final boolean oss) { +void addBuildDockerBaseImage(final String architecture) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${buildDir}/${outputFile}" @@ -221,7 +221,7 @@ void addBuildDockerBaseImage(final String architecture, final boolean oss) { def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' def curlPath = "${buildDir}/static-curl-${architecture}/curl" - final Task buildDockerBaseImageTask = task(taskName("build", architecture, oss, "DockerBaseImage"), type: LoggedExec) { + final Task buildDockerBaseImageTask = task(taskName("build", architecture, false, "DockerBaseImage"), type: LoggedExec) { TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) dependsOn(buildStaticCurlTask) @@ -233,9 +233,9 @@ void addBuildDockerBaseImage(final String architecture, final boolean oss) { executable 'docker' args 'run', '--rm', '-t', '-v', "${buildDir}:/build", - '-v', "${installer}:/tmp/install-baseimage.sh", + '-v', "${installer}:/install-baseimage.sh", '-v', "${curlPath}:/curl", - image, '/tmp/install-baseimage.sh', platform, "/build/${outputFile}" + image, '/install-baseimage.sh', platform, "/build/${outputFile}" } buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } @@ -268,8 +268,8 @@ void addBuildDockerImage(final String architecture, final boolean oss) { for (final String architecture : ["aarch64", "x64"]) { addBuildStaticCurl(architecture) + addBuildDockerBaseImage(architecture) for (final boolean oss : [false, true]) { - addBuildDockerBaseImage(architecture, oss) addCopyDockerContextTask(architecture, oss) addBuildDockerImage(architecture, oss) } From 02ee7f717f489a3a08b1ee45721a5d50c266f981 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 22 Apr 2020 16:18:28 +0100 Subject: [PATCH 28/41] Docs and permissions fixes --- distribution/docker/build.gradle | 8 ++++++-- distribution/docker/src/docker/Dockerfile | 3 --- distribution/docker/src/docker/bin/build-curl.sh | 0 distribution/docker/src/docker/bin/install-baseimage.sh | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) mode change 100644 => 100755 distribution/docker/src/docker/bin/build-curl.sh diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index f19ba0ff93d0b..8f389030ea680 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -189,12 +189,16 @@ task integTest(type: Test) { check.dependsOn integTest +// The `curl` RPM on CentOS pulls in all kinds of dependencies that we +// don't want in the final image. We want to include `curl` because it is +// commonly used for things like readiness checks, or simple diagnostics. +// We therefore compile our own static binary, using Alpine Linux because +// it makes the process of building a static binary much easier. The end +// result still runs fine in e.g. CentOS. void addBuildStaticCurl(final String architecture) { def installer = "${projectDir}/src/docker/bin/build-curl.sh" def workDir = "${buildDir}/static-curl-${architecture}" def outputPath = "${workDir}/curl" - // We build on Alpine as it's easier to create a static executable. The - // result runs just find on CentOS. def image = ("aarch64".equals(architecture) ? 'arm64v8/' : '') + 'alpine:latest' final Task buildStaticCurlTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 05e3c09c2b0f3..c51ea4dc5431d 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -11,9 +11,6 @@ # Set gid=0 and make group perms==owner perms ################################################################################ -# We could use `scratch` and `ADD ${base_image_tar} /` like we do for the -# final image, but we need `curl` if the source line is fetching a remote -# artifact, and that isn't include in the final image. FROM ${builder_image} AS builder ENV PATH /usr/share/elasticsearch/bin:\$PATH diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh old mode 100644 new mode 100755 diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 354f79b6fa881..aab904eed3b9b 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -94,7 +94,7 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# Copy in our mostly-static curl build, that we bind-mounted in +# Copy in our static curl build. This is provided via a Docker bind mount cp /curl "$target"/usr/bin/curl # Curl needs files under here. More importantly, we change Elasticsearch's From 1762bcda4e62f06b0f96a9b5a9af45fb16364bda Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Wed, 22 Apr 2020 16:45:03 +0100 Subject: [PATCH 29/41] Upgrade tini and validate its checksum --- distribution/docker/build.gradle | 15 +++++++++------ .../docker/src/docker/bin/install-baseimage.sh | 8 ++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 8f389030ea680..2a3bf6153fabe 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -96,8 +96,8 @@ project.ext { void addCopyDockerContextTask(final String architecture, final boolean oss) { task(taskName("copy", architecture, oss, "DockerContext"), type: Sync) { - TaskProvider buildBaseImageTask = tasks.named(taskName("build", architecture, false, "DockerBaseImage")) - dependsOn(buildBaseImageTask) + TaskProvider buildBaseLayerTask = tasks.named(taskName("build", architecture, false, "BaseDockerLayer")) + dependsOn(buildBaseLayerTask) expansions(architecture, oss, true).findAll { it.key != 'build_date' }.each { k, v -> inputs.property(k, { v.toString() }) @@ -217,7 +217,10 @@ void addBuildStaticCurl(final String architecture) { buildStaticCurlTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } } -void addBuildDockerBaseImage(final String architecture) { +// In order to reduce the footprint of the Docker image, we build our own +// base filesystem. This allows us to control what makes it inot the final +// image. +void addBuildBaseDockerLayer(final String architecture) { def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" def outputFile = baseImageFilename(architecture) def outputPath = "${buildDir}/${outputFile}" @@ -225,7 +228,7 @@ void addBuildDockerBaseImage(final String architecture) { def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' def curlPath = "${buildDir}/static-curl-${architecture}/curl" - final Task buildDockerBaseImageTask = task(taskName("build", architecture, false, "DockerBaseImage"), type: LoggedExec) { + final Task buildBaseDockerLayerTask = task(taskName("build", architecture, false, "BaseDockerLayer"), type: LoggedExec) { TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) dependsOn(buildStaticCurlTask) @@ -242,7 +245,7 @@ void addBuildDockerBaseImage(final String architecture) { image, '/install-baseimage.sh', platform, "/build/${outputFile}" } - buildDockerBaseImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } + buildBaseDockerLayerTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } } void addBuildDockerImage(final String architecture, final boolean oss) { @@ -272,7 +275,7 @@ void addBuildDockerImage(final String architecture, final boolean oss) { for (final String architecture : ["aarch64", "x64"]) { addBuildStaticCurl(architecture) - addBuildDockerBaseImage(architecture) + addBuildBaseDockerLayer(architecture) for (final boolean oss : [false, true]) { addCopyDockerContextTask(architecture, oss) addBuildDockerImage(architecture, oss) diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index aab904eed3b9b..83ff960648fd0 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -74,8 +74,12 @@ yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ java-latest-openjdk-headless \ bash nc zip upzip pigz -curl --retry 10 -L -o "$target"/bin/tini \ - "https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-$(basename $platform)" +ARCH="$(basename $platform)" +curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH "https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-$ARCH" +curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH.sha256sum "https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-$ARCH.sha256sum" +(cd "$target/bin" && sha256sum -c tini-static-$ARCH.sha256sum) +rm "$target"/bin/tini-static-$ARCH.sha256sum +mv "$target"/bin/tini-static-$ARCH "$target"/bin/tini chmod +x "$target"/bin/tini # Use busybox instead of installing more RPMs, which can pull in all kinds of From 8b3f62447f2258677505816e4698a662180053a4 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Tue, 12 May 2020 15:25:43 +0100 Subject: [PATCH 30/41] Address review feedback --- .../docker/src/docker/bin/build-curl.sh | 16 ++++++--- .../src/docker/bin/install-baseimage.sh | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh index 1c89e6828340b..a004e8cb55b98 100755 --- a/distribution/docker/src/docker/bin/build-curl.sh +++ b/distribution/docker/src/docker/bin/build-curl.sh @@ -1,15 +1,21 @@ #!/bin/sh -# Originally from: https://github.com/dtschan/curl-static/blob/ab8ccc3ff140af860065c04b1e9bcd20bbe2c2d2/build.sh -# License: https://github.com/dtschan/curl-static/blob/ab8ccc3ff1/LICENSE +# This script is based upon the following URL, but it has been modified so that +# Docker is run externally to this script. +# +# https://github.com/dtschan/curl-static/blob/ab8ccc3ff140af860065c04b1e9bcd20bbe2c2d2/build.sh +# +# For license and copyright information, see: +# +# https://github.com/dtschan/curl-static/blob/ab8ccc3ff1/LICENSE # This script must run in an Alpine Linux environment set -e -VERSION=LATEST -# If you prefer a specific version you can set it specifically -# VERSION=7.66.0 +VERSION=7.70.0 +# If you prefer to just build the latest version, use the following line: +# VERSION=LATEST GPG_KEY_URL="https://daniel.haxx.se/mykey.asc" GPG_KEY_PATH="/work/curl-gpg.pub" diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 83ff960648fd0..214262f94b8a4 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -7,6 +7,9 @@ #  # https://github.com/moby/moby/blob/master/contrib/mkimage-yum.sh +declare -r BUSYBOX_VERSION="1.31.0" +declare -r TINI_VERSION="0.19.0" + set -e usage() { @@ -49,21 +52,24 @@ mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 # Install files. We attempt to install a headless Java distro, and exclude a -# number of unnecessary dependencies. In so doing, we also filter out Java itself, -# but since Elasticsearch ships its own JDK, with its own libs, that isn't a problem -# and in fact is what we want. +# number of unnecessary dependencies. In so doing, we also filter out Java +# itself, but since Elasticsearch ships its own JDK, with its own libs, that +# isn't a problem and in fact is what we want. # # Note that we also skip coreutils, as it pulls in all kinds of stuff that # we don't want. # -# Note that I haven't yet verified that these dependencies are, in fact, unnecessary. +# Note that I haven't yet verified that these dependencies are, in fact, +# unnecessary. # # We also include some utilities that we ship with the image. # -# * `nc` is useful for checking network issues -# * `zip` and `unzip` are for working with bundles -# * `pigz` is used for compressing large heaps dumps, and is considerably faster than `gzip` for this task. -# * `tini` is a tiny but valid init for containers. This is used to cleanly control how ES and any child processes are shut down. +# * `nc` is useful for checking network issues. +# * `zip` for working with bundles (BusyBox, below, gives us `unzip`) +# * `pigz` is used for compressing large heaps dumps, and is considerably +# faster than `gzip` for this task. +# * `tini` is a tiny but valid init for containers. This is used to cleanly +# control how ES and any child processes are shut down. # yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y \ @@ -72,11 +78,11 @@ yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --skip-broken \ install \ java-latest-openjdk-headless \ - bash nc zip upzip pigz + bash nc zip pigz ARCH="$(basename $platform)" -curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH "https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-$ARCH" -curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH.sha256sum "https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-$ARCH.sha256sum" +curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static-$ARCH" +curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH.sha256sum "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static-$ARCH.sha256sum" (cd "$target/bin" && sha256sum -c tini-static-$ARCH.sha256sum) rm "$target"/bin/tini-static-$ARCH.sha256sum mv "$target"/bin/tini-static-$ARCH "$target"/bin/tini @@ -84,9 +90,9 @@ chmod +x "$target"/bin/tini # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. There's no RPM for busybox available for CentOS. -BUSYBOX_URL="https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox" +BUSYBOX_URL="https://busybox.net/downloads/binaries/${BUSYBOX_VERSION}-i686-uclibc/busybox" if [[ "$platform" == "linux/arm64" ]]; then - BUSYBOX_URL="https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv8l" + BUSYBOX_URL="https://www.busybox.net/downloads/binaries/${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-armv8l" fi curl --retry 10 -L -o "$target"/bin/busybox "$BUSYBOX_URL" chmod +x "$target"/bin/busybox @@ -104,7 +110,7 @@ cp /curl "$target"/usr/bin/curl # Curl needs files under here. More importantly, we change Elasticsearch's # bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of # the bundled cacerts. -tar cf - /etc/pki | (cd "$target" && tar xf -) +mkdir -p "$target"/etc && cp -a /etc/pki "$target"/etc/ yum --installroot="$target" -y clean all From 02ddf441f395b740c13b6c3688aa89b387667562 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 14 May 2020 14:11:19 +0100 Subject: [PATCH 31/41] WIP - trying for a cross-arch Docker build --- distribution/docker/src/docker/Dockerfile | 23 +------ .../src/docker/bin/install-baseimage.sh | 65 +++++++++---------- 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 9ecb134ff59c9..48531d50ae9e5 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -18,27 +18,6 @@ FROM centos:7 AS builder -# `tini` is a tiny but valid init for containers. This is used to cleanly -# control how ES and any child processes are shut down. -# -# The tini GitHub page gives instructions for verifying the binary using -# gpg, but the keyservers are slow to return the key and this can fail the -# build. Instead, we check the binary against the published checksum. -RUN set -eux ; \\ - \\ - tini_bin="" ; \\ - case "\$(arch)" in \\ - aarch64) tini_bin='tini-arm64' ;; \\ - x86_64) tini_bin='tini-amd64' ;; \\ - *) echo >&2 ; echo >&2 "Unsupported architecture \$(arch)" ; echo >&2 ; exit 1 ;; \\ - esac ; \\ - curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/\${tini_bin} ; \\ - curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/\${tini_bin}.sha256sum ; \\ - sha256sum -c \${tini_bin}.sha256sum ; \\ - rm \${tini_bin}.sha256sum ; \\ - mv \${tini_bin} /tini ; \\ - chmod +x /tini - ENV PATH /usr/share/elasticsearch/bin:\$PATH RUN groupadd -g 1000 elasticsearch && \\ @@ -48,7 +27,7 @@ WORKDIR /usr/share/elasticsearch ${source_elasticsearch} -RUN tar zxf /opt/${elasticsearch} --strip-components=1 +RUN tar zxf /opt/elasticsearch.tar.gz --strip-components=1 RUN sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' /usr/share/elasticsearch/bin/elasticsearch-env RUN mkdir -p config config/jvm.options.d data logs RUN chmod 0775 config config/jvm.options.d data logs diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/install-baseimage.sh index 214262f94b8a4..2b2250d24284d 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/install-baseimage.sh @@ -10,25 +10,26 @@ declare -r BUSYBOX_VERSION="1.31.0" declare -r TINI_VERSION="0.19.0" -set -e - -usage() { - cat < -EOOPTS - exit 1 -} - -platform="$1" -output_file="$2" +BUSYBOX_PATH="" +TINI_BIN="" +case "$(arch)" in + aarch64) + BUSYBOX_PATH="${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-armv8l" + TINI_BIN='tini-arm64' + ;; + x86_64) + BUSYBOX_PATH="${BUSYBOX_VERSION}-i686-uclibc/busybox" + TINI_BIN='tini-amd64' + ;; + *) + echo >&2 + echo >&2 "Unsupported architecture $(arch)" + echo >&2 + exit 1 + ;; +esac -if [[ -z $platform ]]; then - usage -fi - -if [[ -z "$output_file" ]]; then - usage -fi +set -e # Start off with an up-to-date system yum update --setopt=tsflags=nodocs -y @@ -80,21 +81,19 @@ yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ java-latest-openjdk-headless \ bash nc zip pigz -ARCH="$(basename $platform)" -curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static-$ARCH" -curl --retry 10 -L -o "$target"/bin/tini-static-$ARCH.sha256sum "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static-$ARCH.sha256sum" -(cd "$target/bin" && sha256sum -c tini-static-$ARCH.sha256sum) -rm "$target"/bin/tini-static-$ARCH.sha256sum -mv "$target"/bin/tini-static-$ARCH "$target"/bin/tini -chmod +x "$target"/bin/tini +# The tini GitHub page gives instructions for verifying the binary using +# gpg, but the keyservers are slow to return the key and this can fail the +# build. Instead, we check the binary against the published checksum. +curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/${TINI_BIN} +curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/${TINI_BIN}.sha256sum +sha256sum -c ${TINI_BIN}.sha256sum +rm ${TINI_BIN}.sha256sum +mv ${TINI_BIN} /tini +chmod +x /tini # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. There's no RPM for busybox available for CentOS. -BUSYBOX_URL="https://busybox.net/downloads/binaries/${BUSYBOX_VERSION}-i686-uclibc/busybox" -if [[ "$platform" == "linux/arm64" ]]; then - BUSYBOX_URL="https://www.busybox.net/downloads/binaries/${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-armv8l" -fi -curl --retry 10 -L -o "$target"/bin/busybox "$BUSYBOX_URL" +curl --retry 10 -L -o "$target"/bin/busybox "https://busybox.net/downloads/binaries/$BUSYBOX_PATH" chmod +x "$target"/bin/busybox set +x @@ -104,8 +103,8 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# Copy in our static curl build. This is provided via a Docker bind mount -cp /curl "$target"/usr/bin/curl +# Copy in our static curl build. +cp /curl-$(arch) "$target"/usr/bin/curl # Curl needs files under here. More importantly, we change Elasticsearch's # bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of @@ -152,4 +151,4 @@ mkdir -p --mode=0755 "$target"/var/cache/ldconfig # Write out the base filesystem. The -C option changes directory to $target, # so '.' refers to that directory -tar czf "$output_file" --numeric-owner -C "$target" . +tar czf /base-filesystem.tar.gz --numeric-owner -C "$target" . From 00c3cb88671bb4df2297ad6a43c1dee1bb08f6bd Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Fri, 12 Jun 2020 22:02:17 +0100 Subject: [PATCH 32/41] Perform all the build steps via the Dockerfile This means that Gradle can't cache any of the intermediate steps, but Docker still can. This change makes it easier for the build to be cross-architecture. --- distribution/docker/build.gradle | 75 +------------------ distribution/docker/src/docker/Dockerfile | 42 +++++++---- .../docker/src/docker/bin/build-curl.sh | 2 + .../{install-baseimage.sh => build-rootfs.sh} | 14 +--- 4 files changed, 34 insertions(+), 99 deletions(-) rename distribution/docker/src/docker/bin/{install-baseimage.sh => build-rootfs.sh} (93%) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 1062e915d7e67..3e36a00e4df89 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -27,10 +27,6 @@ dependencies { ossDockerSource project(path: ":distribution:archives:oss-linux-tar") } -private static String baseImageFilename(final String architecture) { - return "elasticsearch-baseimage-${architecture}.tar.gz" -} - ext.expansions = { architecture, oss, local -> String classifier if (local) { @@ -65,11 +61,10 @@ ext.expansions = { architecture, oss, local -> RUN curl --retry 8 -S -L \\ --output /opt/elasticsearch.tar.gz \\ https://artifacts.elastic.co/downloads/elasticsearch/$elasticsearch -""" +""".trim() } return [ - 'base_image_tar' : baseImageFilename(architecture), 'build_date' : BuildParams.buildDate, 'git_revision' : BuildParams.gitRevision, 'license' : oss ? 'Apache-2.0' : 'Elastic-License', @@ -107,19 +102,12 @@ project.ext { from(project.projectDir.toPath().resolve("src/docker/Dockerfile")) { expand(expansions(architecture, oss, local)) } - - from("${projectDir}/build") { - include baseImageFilename(architecture) - } } } } void addCopyDockerContextTask(final String architecture, final boolean oss) { task(taskName("copy", architecture, oss, "DockerContext"), type: Sync) { - TaskProvider buildBaseLayerTask = tasks.named(taskName("build", architecture, false, "BaseDockerLayer")) - dependsOn(buildBaseLayerTask) - expansions(architecture, oss, true).findAll { it.key != 'build_date' }.each { k, v -> inputs.property(k, { v.toString() }) } @@ -210,65 +198,6 @@ task integTest(type: Test) { check.dependsOn integTest -// The `curl` RPM on CentOS pulls in all kinds of dependencies that we -// don't want in the final image. We want to include `curl` because it is -// commonly used for things like readiness checks, or simple diagnostics. -// We therefore compile our own static binary, using Alpine Linux because -// it makes the process of building a static binary much easier. The end -// result still runs fine in e.g. CentOS. -void addBuildStaticCurl(final String architecture) { - def installer = "${projectDir}/src/docker/bin/build-curl.sh" - def workDir = "${buildDir}/static-curl-${architecture}" - def outputPath = "${workDir}/curl" - def image = ("aarch64".equals(architecture) ? 'arm64v8/' : '') + 'alpine:latest' - - final Task buildStaticCurlTask = task(taskName("build", architecture, false, "StaticCurl"), type: LoggedExec) { - inputs.file(installer).withPropertyName('static curl binary builder').withPathSensitivity(PathSensitivity.RELATIVE) - inputs.property('architecture', architecture) - outputs.file(outputPath) - outputs.cacheIf({ true }) - - executable 'docker' - args 'run', '--rm', '-t', - '-v', "${workDir}:/work", - '-v', "${installer}:/build-curl.sh", - image, '/build-curl.sh' - } - - buildStaticCurlTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } -} - -// In order to reduce the footprint of the Docker image, we build our own -// base filesystem. This allows us to control what makes it inot the final -// image. -void addBuildBaseDockerLayer(final String architecture) { - def installer = "${projectDir}/src/docker/bin/install-baseimage.sh" - def outputFile = baseImageFilename(architecture) - def outputPath = "${buildDir}/${outputFile}" - def platform = "aarch64".equals(architecture) ? 'linux/arm64' : 'linux/amd64' - def image = "aarch64".equals(architecture) ? 'arm64v8/centos:7' : 'centos:7' - def curlPath = "${buildDir}/static-curl-${architecture}/curl" - - final Task buildBaseDockerLayerTask = task(taskName("build", architecture, false, "BaseDockerLayer"), type: LoggedExec) { - TaskProvider buildStaticCurlTask = tasks.named(taskName("build", architecture, false, "StaticCurl")) - dependsOn(buildStaticCurlTask) - - inputs.file(installer).withPropertyName('base filesystem builder').withPathSensitivity(PathSensitivity.RELATIVE) - inputs.property('architecture', architecture) - outputs.file(outputPath) - outputs.cacheIf({ true }) - - executable 'docker' - args 'run', '--rm', '-t', - '-v', "${buildDir}:/build", - '-v', "${installer}:/install-baseimage.sh", - '-v', "${curlPath}:/curl", - image, '/install-baseimage.sh', platform, "/build/${outputFile}" - } - - buildBaseDockerLayerTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) } -} - void addBuildDockerImage(final String architecture, final boolean oss) { final Task buildDockerImageTask = task(taskName("build", architecture, oss, "DockerImage"), type: DockerBuildTask) { TaskProvider copyContextTask = tasks.named(taskName("copy", architecture, oss, "DockerContext")) @@ -295,8 +224,6 @@ void addBuildDockerImage(final String architecture, final boolean oss) { } for (final String architecture : ["aarch64", "x64"]) { - addBuildStaticCurl(architecture) - addBuildBaseDockerLayer(architecture) for (final boolean oss : [false, true]) { addCopyDockerContextTask(architecture, oss) addBuildDockerImage(architecture, oss) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 48531d50ae9e5..cb6f99a392fb7 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -10,24 +10,40 @@ https://docs.groovy-lang.org/latest/html/api/groovy/text/SimpleTemplateEngine.html */ %> + ################################################################################ -# Build stage 0 `builder`: -# Extract elasticsearch artifact -# Set gid=0 and make group perms==owner perms +# Step 1. Build curl statically. Installing it from RPM on CentOS pulls in too +# many dependencies. ################################################################################ +FROM alpine:latest AS curl +COPY bin/build-curl.sh / +RUN /build-curl.sh -FROM centos:7 AS builder -ENV PATH /usr/share/elasticsearch/bin:\$PATH +################################################################################ +# Step 2. Create a minimal root filesystem directory. This will form the basis +# for our image. +################################################################################ +FROM centos:7 AS rootfs +COPY bin/build-rootfs.sh / +RUN /build-rootfs.sh /rootfs +COPY --from=curl /work/curl /rootfs/usr/bin/curl -RUN groupadd -g 1000 elasticsearch && \\ - adduser -u 1000 -g 1000 -d /usr/share/elasticsearch elasticsearch +################################################################################ +# Step 3. Fetch the Elasticsearch distribution and configure it for Docker +################################################################################ +FROM centos:7 AS builder + +RUN mkdir -p /usr/share/elasticsearch WORKDIR /usr/share/elasticsearch +# Fetch the appropriate Elasticsearch distribution for this architecture ${source_elasticsearch} RUN tar zxf /opt/elasticsearch.tar.gz --strip-components=1 + +# Configure the distribution for Docker RUN sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' /usr/share/elasticsearch/bin/elasticsearch-env RUN mkdir -p config config/jvm.options.d data logs RUN chmod 0775 config config/jvm.options.d data logs @@ -35,20 +51,16 @@ COPY config/elasticsearch.yml config/log4j2.properties config/ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties ################################################################################ -# Build stage 1 (the actual elasticsearch image): -# Copy elasticsearch from stage 0 -# Add entrypoint +# Step 4. Build the final image, using the rootfs above as the basis, and +# copying in the Elasticsearch distribution ################################################################################ - -FROM --platform=${platform} scratch +FROM scratch # Setup the initial filesystem. -ADD ${base_image_tar} / +COPY --from=rootfs /rootfs / ENV ELASTIC_CONTAINER true -# Most of the utilities are provided with Busybox, hence the different user -# management commands here compared to the builder image. RUN addgroup -g 1000 elasticsearch && \\ adduser -D -u 1000 -G elasticsearch -g elasticsearch -h /usr/share/elasticsearch elasticsearch && \\ addgroup elasticsearch root && \\ diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh index a004e8cb55b98..4c6b5ea531802 100755 --- a/distribution/docker/src/docker/bin/build-curl.sh +++ b/distribution/docker/src/docker/bin/build-curl.sh @@ -20,6 +20,8 @@ GPG_KEY_URL="https://daniel.haxx.se/mykey.asc" GPG_KEY_PATH="/work/curl-gpg.pub" +mkdir /work + # Print failure message if we exit unexpectedly trap 'RC="$?"; echo "*** FAILED! RC=${RC}"; exit ${RC}' EXIT diff --git a/distribution/docker/src/docker/bin/install-baseimage.sh b/distribution/docker/src/docker/bin/build-rootfs.sh similarity index 93% rename from distribution/docker/src/docker/bin/install-baseimage.sh rename to distribution/docker/src/docker/bin/build-rootfs.sh index 2b2250d24284d..a2af98869ebf4 100755 --- a/distribution/docker/src/docker/bin/install-baseimage.sh +++ b/distribution/docker/src/docker/bin/build-rootfs.sh @@ -35,7 +35,8 @@ set -e yum update --setopt=tsflags=nodocs -y # Create a temporary directory into which we will install files -target=$(mktemp -d --tmpdir $(basename $0).XXXXXX) +target="$1" +mkdir $target set -x @@ -88,8 +89,8 @@ curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19. curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/${TINI_BIN}.sha256sum sha256sum -c ${TINI_BIN}.sha256sum rm ${TINI_BIN}.sha256sum -mv ${TINI_BIN} /tini -chmod +x /tini +mv ${TINI_BIN} "$target"/bin/tini +chmod +x "$target"/bin/tini # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. There's no RPM for busybox available for CentOS. @@ -103,9 +104,6 @@ for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do done set -x -# Copy in our static curl build. -cp /curl-$(arch) "$target"/usr/bin/curl - # Curl needs files under here. More importantly, we change Elasticsearch's # bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of # the bundled cacerts. @@ -148,7 +146,3 @@ rm -rf \ # ldconfig rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig mkdir -p --mode=0755 "$target"/var/cache/ldconfig - -# Write out the base filesystem. The -C option changes directory to $target, -# so '.' refers to that directory -tar czf /base-filesystem.tar.gz --numeric-owner -C "$target" . From 66bec1b655e4ca1b28a2c015319bc1124a9dcb3c Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 12:21:10 +0100 Subject: [PATCH 33/41] Explicitly specify packages to install instead of hacky exclusion fiasco --- .../docker/src/docker/bin/build-rootfs.sh | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/distribution/docker/src/docker/bin/build-rootfs.sh b/distribution/docker/src/docker/bin/build-rootfs.sh index a2af98869ebf4..6a3a3313308a5 100755 --- a/distribution/docker/src/docker/bin/build-rootfs.sh +++ b/distribution/docker/src/docker/bin/build-rootfs.sh @@ -53,34 +53,18 @@ mknod -m 666 "$target"/dev/tty0 c 4 0 mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 -# Install files. We attempt to install a headless Java distro, and exclude a -# number of unnecessary dependencies. In so doing, we also filter out Java -# itself, but since Elasticsearch ships its own JDK, with its own libs, that -# isn't a problem and in fact is what we want. -# -# Note that we also skip coreutils, as it pulls in all kinds of stuff that -# we don't want. -# -# Note that I haven't yet verified that these dependencies are, in fact, -# unnecessary. -# -# We also include some utilities that we ship with the image. -# -# * `nc` is useful for checking network issues. -# * `zip` for working with bundles (BusyBox, below, gives us `unzip`) -# * `pigz` is used for compressing large heaps dumps, and is considerably -# faster than `gzip` for this task. -# * `tini` is a tiny but valid init for containers. This is used to cleanly -# control how ES and any child processes are shut down. -# +# Install a minimal set of dependencies yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y \ - -x copy-jdk-configs -x cups-libs -x javapackages-tools -x alsa-lib -x freetype -x libjpeg -x libjpeg-turbo \ - -x coreutils \ --skip-broken \ install \ - java-latest-openjdk-headless \ - bash nc zip pigz + basesystem \ + bash \ + glibc \ + libstdc++ \ + pigz \ + zip \ + zlib # The tini GitHub page gives instructions for verifying the binary using # gpg, but the keyservers are slow to return the key and this can fail the @@ -98,8 +82,8 @@ curl --retry 10 -L -o "$target"/bin/busybox "https://busybox.net/downloads/binar chmod +x "$target"/bin/busybox set +x -# Add links for all the utilities (except sh, as we have bash) -for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh ); do +# Add links for all the utilities (except sh, as we have bash, and some others) +for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh | grep -v telnet ); do ln "$target"/bin/busybox "$target"/$path done set -x @@ -121,11 +105,12 @@ rm -rf \ "$target"/etc/yum* \ "$target"/sbin/sln \ "$target"/usr/bin/rpm \ - "$target"/usr/bin/tini-static \ + "$target"/{usr,var}/games \ "$target"/usr/lib/dracut \ "$target"/usr/lib/systemd \ "$target"/usr/lib/udev \ - "${target}/usr/local" \ + "$target"/usr/lib64/X11 \ + "$target"/usr/local \ "$target"/usr/share/awk \ "$target"/usr/share/centos-release \ "$target"/usr/share/cracklib \ @@ -136,7 +121,7 @@ rm -rf \ "$target"/usr/share/licenses \ "$target"/usr/share/xsessions \ "$target"/usr/share/zoneinfo \ - "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons} \ + "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons,pixmaps,sounds,backgrounds,themes,X11} \ "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} \ "$target"/var/cache/yum \ "$target"/var/lib/rpm \ From a508421ab5b0422519d2ee6c3fef703e53467273 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 12:28:23 +0100 Subject: [PATCH 34/41] Tweak comment --- distribution/docker/src/docker/bin/build-rootfs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/docker/src/docker/bin/build-rootfs.sh b/distribution/docker/src/docker/bin/build-rootfs.sh index 6a3a3313308a5..aaf801ce45f14 100755 --- a/distribution/docker/src/docker/bin/build-rootfs.sh +++ b/distribution/docker/src/docker/bin/build-rootfs.sh @@ -53,7 +53,7 @@ mknod -m 666 "$target"/dev/tty0 c 4 0 mknod -m 666 "$target"/dev/urandom c 1 9 mknod -m 666 "$target"/dev/zero c 1 5 -# Install a minimal set of dependencies +# Install a minimal set of dependencies, as well as some utilties (zip, pigz) yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y \ --skip-broken \ From f9e9e6a7ba623426a5de156777b24d54b66cefbb Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 13:34:09 +0100 Subject: [PATCH 35/41] Add more into to ProcessInfo Javadoc --- .../org/elasticsearch/packaging/util/ProcessInfo.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java index 23ba400af2f11..58dc5f16ce128 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/ProcessInfo.java @@ -27,9 +27,11 @@ /** * Encapsulates the fetching of information about a running process. - * - * This is helpful on stripped-down images where, in order to fetch full information about a process, - * we have to consult /proc. + *

+ * This is helpful on stripped-down Docker images where, in order to fetch full information about a process, + * we have to consult /proc. Although this class hides the implementation details, it only + * works in Linux containers. At the moment that isn't a problem, because we only publish Docker images + * for Linux. */ public class ProcessInfo { public final int pid; From 0daa8403c6bfdab8d331d17847902e2ea7bce74a Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 13:53:50 +0100 Subject: [PATCH 36/41] Use musl busybox for both archs --- distribution/docker/src/docker/bin/build-rootfs.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/distribution/docker/src/docker/bin/build-rootfs.sh b/distribution/docker/src/docker/bin/build-rootfs.sh index aaf801ce45f14..5a47f62e60d4d 100755 --- a/distribution/docker/src/docker/bin/build-rootfs.sh +++ b/distribution/docker/src/docker/bin/build-rootfs.sh @@ -10,15 +10,15 @@ declare -r BUSYBOX_VERSION="1.31.0" declare -r TINI_VERSION="0.19.0" -BUSYBOX_PATH="" +BUSYBOX_ARCH="" TINI_BIN="" case "$(arch)" in aarch64) - BUSYBOX_PATH="${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-armv8l" + BUSYBOX_ARCH="armv8l" TINI_BIN='tini-arm64' ;; x86_64) - BUSYBOX_PATH="${BUSYBOX_VERSION}-i686-uclibc/busybox" + BUSYBOX_ARCH="x86_64" TINI_BIN='tini-amd64' ;; *) @@ -78,7 +78,8 @@ chmod +x "$target"/bin/tini # Use busybox instead of installing more RPMs, which can pull in all kinds of # stuff we don't want. There's no RPM for busybox available for CentOS. -curl --retry 10 -L -o "$target"/bin/busybox "https://busybox.net/downloads/binaries/$BUSYBOX_PATH" +curl --retry 10 -L -o "$target"/bin/busybox \ + "https://busybox.net/downloads/binaries/${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-${BUSYBOX_ARCH}" chmod +x "$target"/bin/busybox set +x From 7eedc3f3dc0ca30572066d1a636f55cf2c3a8744 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 14:21:59 +0100 Subject: [PATCH 37/41] Update docs --- docs/reference/setup/install/docker.asciidoc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/reference/setup/install/docker.asciidoc b/docs/reference/setup/install/docker.asciidoc index f55d71bfcc838..27d70fdb5f300 100644 --- a/docs/reference/setup/install/docker.asciidoc +++ b/docs/reference/setup/install/docker.asciidoc @@ -1,8 +1,9 @@ [[docker]] === Install {es} with Docker -{es} is also available as Docker images. -The images use https://hub.docker.com/_/centos/[centos:7] as the base image. +{es} is also available as Docker images. Starting with version 7.9.0, these +are based upon a tiny core of essential files. Prior versions used +https://hub.docker.com/_/centos/[centos:7] as the base image. A list of all published Docker images and tags is available at https://www.docker.elastic.co[www.docker.elastic.co]. The source files @@ -412,9 +413,10 @@ You must explicitly accept them either by: See {plugins}/_other_command_line_parameters.html[Plugin management] for more information. -The {es} Docker image tries to include only what is required to run {es}, and therefore does not provide a -package manager. It is possible to add additional utilities with a multi-phase Docker build. You must also -copy any dependencies, for example shared libraries. +The {es} Docker image only includes what is required to run {es}, and does +not provide a package manager. It is possible to add additional utilities +with a multi-phase Docker build. You must also copy any dependencies, for +example shared libraries. [source,sh,subs="attributes"] -------------------------------------------- @@ -426,8 +428,8 @@ COPY --from=builder /usr/bin/some-utility /usr/bin/ COPY --from=builder /usr/lib/some-lib.so /usr/lib/ -------------------------------------------- -You should use `centos:7` as a base, in order to avoid incompatibilities, and you can use -http://man7.org/linux/man-pages/man1/ldd.1.html[`ldd`] to list the shared libraries required -by a utility. +You should use `centos:7` as a base in order to avoid incompatibilities. +Use http://man7.org/linux/man-pages/man1/ldd.1.html[`ldd`] to list the +shared libraries required by a utility. include::next-steps.asciidoc[] From ccc1209a597cd132b3dc573d7f6074e53719ec3c Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 25 Jun 2020 14:23:54 +0100 Subject: [PATCH 38/41] Test tweak --- .../src/test/java/org/elasticsearch/packaging/util/Docker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index e86ed9b5168ea..db23b24e465f8 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -496,7 +496,7 @@ private static void verifyOssInstallation(Installation es) { Stream.of("zip", "unzip", "pigz", "nc") .forEach( cliBinary -> assertTrue( - cliBinary + " ought to be installed. ", + cliBinary + " ought to be available.", dockerShell.runIgnoreExitCode("which " + cliBinary).isSuccess() ) ); From 6535024c278b01b3e7100b3440b54ea4d5672815 Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Fri, 26 Jun 2020 14:52:49 +0100 Subject: [PATCH 39/41] Follow Docker guidelines and put script contents into Dockerfile --- distribution/docker/src/docker/Dockerfile | 125 +++++++++++++++- .../docker/src/docker/bin/build-curl.sh | 86 ----------- .../docker/src/docker/bin/build-rootfs.sh | 134 ------------------ .../elasticsearch/packaging/util/Docker.java | 3 +- 4 files changed, 121 insertions(+), 227 deletions(-) delete mode 100755 distribution/docker/src/docker/bin/build-curl.sh delete mode 100755 distribution/docker/src/docker/bin/build-rootfs.sh diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index cb6f99a392fb7..6b0c6476fac89 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -16,19 +16,134 @@ # many dependencies. ################################################################################ FROM alpine:latest AS curl -COPY bin/build-curl.sh / -RUN /build-curl.sh +ENV VERSION 7.71.0 +ENV TARBALL_URL https://curl.haxx.se/download/curl-\${VERSION}.tar.xz +ENV TARBALL_PATH curl-\${VERSION}.tar.xz + +# Install dependencies +RUN apk add gnupg gcc make musl-dev openssl-dev openssl-libs-static file + +RUN mkdir /work +WORKDIR /work + +# Fetch curl sources and files for validation +RUN wget "https://daniel.haxx.se/mykey.asc" -O "curl-gpg.pub" && \\ + wget "\${TARBALL_URL}.asc" -O "\${TARBALL_PATH}.asc" && \\ + wget "\${TARBALL_URL}" -O "\${TARBALL_PATH}" + +# Validate source +RUN gpg --import --always-trust "curl-gpg.pub" && \\ + gpg --verify "\${TARBALL_PATH}.asc" "\${TARBALL_PATH}" + +# Unpack and build +RUN tar xfJ "\${TARBALL_PATH}" && \\ + cd "curl-\${VERSION}" && \\ + ./configure --disable-shared --with-ca-fallback --with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt && \\ + make curl_LDFLAGS="-all-static" && \\ + cp src/curl /work/curl && \\ + strip /work/curl ################################################################################ # Step 2. Create a minimal root filesystem directory. This will form the basis # for our image. ################################################################################ FROM centos:7 AS rootfs -COPY bin/build-rootfs.sh / -RUN /build-rootfs.sh /rootfs -COPY --from=curl /work/curl /rootfs/usr/bin/curl +ENV BUSYBOX_VERSION 1.31.0 +ENV TINI_VERSION 0.19.0 + +# Start off with an up-to-date system +RUN yum update --setopt=tsflags=nodocs -y + +# Create a directory into which we will install files +RUN mkdir /rootfs + +# Create required devices +RUN mkdir -m 755 /rootfs/dev && \\ + mknod -m 600 /rootfs/dev/console c 5 1 && \\ + mknod -m 600 /rootfs/dev/initctl p && \\ + mknod -m 666 /rootfs/dev/full c 1 7 && \\ + mknod -m 666 /rootfs/dev/null c 1 3 && \\ + mknod -m 666 /rootfs/dev/ptmx c 5 2 && \\ + mknod -m 666 /rootfs/dev/random c 1 8 && \\ + mknod -m 666 /rootfs/dev/tty c 5 0 && \\ + mknod -m 666 /rootfs/dev/tty0 c 4 0 && \\ + mknod -m 666 /rootfs/dev/urandom c 1 9 && \\ + mknod -m 666 /rootfs/dev/zero c 1 5 + +# Install a minimal set of dependencies, and some for Elasticsearch +RUN yum --installroot=/rootfs --releasever=/ --setopt=tsflags=nodocs \\ + --setopt=group_package_types=mandatory -y \\ + --skip-broken \\ + install \\ + basesystem bash glibc libstdc++ zip zlib + +# The tini GitHub page gives instructions for verifying the binary using +# gpg, but the keyservers are slow to return the key and this can fail the +# build. Instead, we check the binary against the published checksum. +# +# Also, we use busybox instead of installing utility RPMs, which pulls in +# all kinds of stuff we don't want. +RUN set -e ; \\ + TINI_BIN="" ; \\ + BUSYBOX_ARCH="" ; \\ + case "\$(arch)" in \\ + aarch64) \\ + BUSYBOX_ARCH='armv8l' ; \\ + TINI_BIN='tini-arm64' ; \\ + ;; \\ + x86_64) \\ + BUSYBOX_ARCH='x86_64' ; \\ + TINI_BIN='tini-amd64' ; \\ + ;; \\ + *) echo >&2 "Unsupported architecture \$(arch)" ; exit 1 ;; \\ + esac ; \\ + curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/v0.19.0/\${TINI_BIN}" ; \\ + curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/v0.19.0/\${TINI_BIN}.sha256sum" ; \\ + sha256sum -c "\${TINI_BIN}.sha256sum" ; \\ + rm "\${TINI_BIN}.sha256sum" ; \\ + mv "\${TINI_BIN}" /rootfs/bin/tini ; \\ + chmod +x /rootfs/bin/tini ; \\ + curl --retry 10 -L -o /rootfs/bin/busybox \\ + "https://busybox.net/downloads/binaries/\${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-\${BUSYBOX_ARCH}" ; \\ + chmod +x /rootfs/bin/busybox + +# Add links for most of the Busybox utilities +RUN set -e ; \\ + for path in \$( /rootfs/bin/busybox --list-full | grep -v bin/sh | grep -v telnet ); do \\ + ln /rootfs/bin/busybox /rootfs/\$path ; \\ + done + +# Curl needs files under here. More importantly, we change Elasticsearch's +# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of +# the bundled cacerts. +RUN mkdir -p /rootfs/etc && \\ + cp -a /etc/pki /rootfs/etc/ + +# Cleanup the filesystem +RUN yum --installroot=/rootfs -y clean all && \\ + cd /rootfs && \\ + rm -rf \\ + etc/{X11,centos-release*,csh*,profile*,skel*,yum*} \\ + sbin/sln \\ + usr/bin/rpm \\ + {usr,var}/games \\ + usr/lib/{dracut,systemd,udev} \\ + usr/lib64/X11 \\ + usr/local \\ + usr/share/{awk,centos-release,cracklib,desktop-directories,gcc-*,i18n,icons,licenses,xsessions,zoneinfo} \\ + usr/share/{man,doc,info,games,gdb,ghostscript,gnome,groff,icons,pixmaps,sounds,backgrounds,themes,X11} \\ + usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} \\ + var/cache/yum \\ + var/lib/{rpm,yum} \\ + var/log/yum.log + +# ldconfig +RUN rm -rf /rootfs/etc/ld.so.cache /rootfs/var/cache/ldconfig && \\ + mkdir -p --mode=0755 /rootfs/var/cache/ldconfig + +COPY --from=curl /work/curl /rootfs/usr/bin/curl ################################################################################ # Step 3. Fetch the Elasticsearch distribution and configure it for Docker diff --git a/distribution/docker/src/docker/bin/build-curl.sh b/distribution/docker/src/docker/bin/build-curl.sh deleted file mode 100755 index 4c6b5ea531802..0000000000000 --- a/distribution/docker/src/docker/bin/build-curl.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/sh - -# This script is based upon the following URL, but it has been modified so that -# Docker is run externally to this script. -# -# https://github.com/dtschan/curl-static/blob/ab8ccc3ff140af860065c04b1e9bcd20bbe2c2d2/build.sh -# -# For license and copyright information, see: -# -# https://github.com/dtschan/curl-static/blob/ab8ccc3ff1/LICENSE - -# This script must run in an Alpine Linux environment - -set -e - -VERSION=7.70.0 -# If you prefer to just build the latest version, use the following line: -# VERSION=LATEST -GPG_KEY_URL="https://daniel.haxx.se/mykey.asc" -GPG_KEY_PATH="/work/curl-gpg.pub" - - -mkdir /work - -# Print failure message if we exit unexpectedly -trap 'RC="$?"; echo "*** FAILED! RC=${RC}"; exit ${RC}' EXIT - -# Fetch a url to a location unless it already exists -conditional_fetch () { - local URL=$1 - local OUTPUT_PATH=$2 - if [ -e ${OUTPUT_PATH} ]; then - echo "Found existing ${OUTPUT_PATH}; reusing..." - else - echo "Fetching ${URL} to ${OUTPUT_PATH}..." - wget "${URL}" -O "${OUTPUT_PATH}" - fi -} - -# Determine tarball filename -if [ "$VERSION" = 'LATEST' ]; then - echo "Determining latest version..." - TARBALL_FILENAME=$(wget "https://curl.haxx.se/download/?C=M;O=D" -q -O- | grep -w -m 1 -o 'curl-.*\.tar\.xz"' | sed 's/"$//') -else - TARBALL_FILENAME=curl-${VERSION}.tar.xz -fi - -# Set some variables (depends on tarball filename determined above) -TARBALL_URL=https://curl.haxx.se/download/${TARBALL_FILENAME} -TARBALL_PATH=/work/${TARBALL_FILENAME} -FINAL_BIN_PATH=/work/curl - -echo "*** Fetching ${TARBALL_FILENAME} and files to validate it..." -conditional_fetch "${GPG_KEY_URL}" "${GPG_KEY_PATH}" -conditional_fetch "${TARBALL_URL}.asc" "${TARBALL_PATH}.asc" -conditional_fetch "${TARBALL_URL}" "${TARBALL_PATH}" - -echo "*** Validating source..." -apk add gnupg -gpg --import --always-trust ${GPG_KEY_PATH} -gpg --verify ${TARBALL_PATH}.asc ${TARBALL_PATH} - -echo "*** Unpacking source..." -tar xfJ ${TARBALL_PATH} -cd curl-* - -echo "*** Installing build dependencies..." -apk add gcc make musl-dev openssl-dev openssl-libs-static file - -echo "*** configuring..." -# The bundle path is set so that our `curl` can fetch an https URL on -# CentOS -./configure --disable-shared --with-ca-fallback --with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt -echo "making..." -make curl_LDFLAGS=-all-static - -echo "*** Finishing up..." -cp src/curl ${FINAL_BIN_PATH} -strip ${FINAL_BIN_PATH} -chown $(id -u):$(id -g) ${FINAL_BIN_PATH} - -# Clear the trap so when we exit there is no failure message -trap - EXIT -echo SUCCESS -ls -ld ${FINAL_BIN_PATH} -du -h ${FINAL_BIN_PATH} diff --git a/distribution/docker/src/docker/bin/build-rootfs.sh b/distribution/docker/src/docker/bin/build-rootfs.sh deleted file mode 100755 index 5a47f62e60d4d..0000000000000 --- a/distribution/docker/src/docker/bin/build-rootfs.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash -# -# Create a basic filesystem that can be used to create a Docker images that -# don't require a full distro. -# -# Originally from: -#  -# https://github.com/moby/moby/blob/master/contrib/mkimage-yum.sh - -declare -r BUSYBOX_VERSION="1.31.0" -declare -r TINI_VERSION="0.19.0" - -BUSYBOX_ARCH="" -TINI_BIN="" -case "$(arch)" in - aarch64) - BUSYBOX_ARCH="armv8l" - TINI_BIN='tini-arm64' - ;; - x86_64) - BUSYBOX_ARCH="x86_64" - TINI_BIN='tini-amd64' - ;; - *) - echo >&2 - echo >&2 "Unsupported architecture $(arch)" - echo >&2 - exit 1 - ;; -esac - -set -e - -# Start off with an up-to-date system -yum update --setopt=tsflags=nodocs -y - -# Create a temporary directory into which we will install files -target="$1" -mkdir $target - -set -x - -# Create required devices -mkdir -m 755 "$target"/dev -mknod -m 600 "$target"/dev/console c 5 1 -mknod -m 600 "$target"/dev/initctl p -mknod -m 666 "$target"/dev/full c 1 7 -mknod -m 666 "$target"/dev/null c 1 3 -mknod -m 666 "$target"/dev/ptmx c 5 2 -mknod -m 666 "$target"/dev/random c 1 8 -mknod -m 666 "$target"/dev/tty c 5 0 -mknod -m 666 "$target"/dev/tty0 c 4 0 -mknod -m 666 "$target"/dev/urandom c 1 9 -mknod -m 666 "$target"/dev/zero c 1 5 - -# Install a minimal set of dependencies, as well as some utilties (zip, pigz) -yum --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ - --setopt=group_package_types=mandatory -y \ - --skip-broken \ - install \ - basesystem \ - bash \ - glibc \ - libstdc++ \ - pigz \ - zip \ - zlib - -# The tini GitHub page gives instructions for verifying the binary using -# gpg, but the keyservers are slow to return the key and this can fail the -# build. Instead, we check the binary against the published checksum. -curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/${TINI_BIN} -curl --retry 8 -S -L -O https://github.com/krallin/tini/releases/download/v0.19.0/${TINI_BIN}.sha256sum -sha256sum -c ${TINI_BIN}.sha256sum -rm ${TINI_BIN}.sha256sum -mv ${TINI_BIN} "$target"/bin/tini -chmod +x "$target"/bin/tini - -# Use busybox instead of installing more RPMs, which can pull in all kinds of -# stuff we don't want. There's no RPM for busybox available for CentOS. -curl --retry 10 -L -o "$target"/bin/busybox \ - "https://busybox.net/downloads/binaries/${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-${BUSYBOX_ARCH}" -chmod +x "$target"/bin/busybox - -set +x -# Add links for all the utilities (except sh, as we have bash, and some others) -for path in $( "$target"/bin/busybox --list-full | grep -v bin/sh | grep -v telnet ); do - ln "$target"/bin/busybox "$target"/$path -done -set -x - -# Curl needs files under here. More importantly, we change Elasticsearch's -# bundled JDK to use /etc/pki/ca-trust/extracted/java/cacerts instead of -# the bundled cacerts. -mkdir -p "$target"/etc && cp -a /etc/pki "$target"/etc/ - -yum --installroot="$target" -y clean all - -rm -rf \ - "$target"/etc/X11 \ - "$target"/etc/centos-release* \ - "$target"/etc/csh* \ - "$target"/etc/groff \ - "$target"/etc/profile* \ - "$target"/etc/skel* \ - "$target"/etc/yum* \ - "$target"/sbin/sln \ - "$target"/usr/bin/rpm \ - "$target"/{usr,var}/games \ - "$target"/usr/lib/dracut \ - "$target"/usr/lib/systemd \ - "$target"/usr/lib/udev \ - "$target"/usr/lib64/X11 \ - "$target"/usr/local \ - "$target"/usr/share/awk \ - "$target"/usr/share/centos-release \ - "$target"/usr/share/cracklib \ - "$target"/usr/share/desktop-directories \ - "$target"/usr/share/gcc-* \ - "$target"/usr/share/i18n \ - "$target"/usr/share/icons \ - "$target"/usr/share/licenses \ - "$target"/usr/share/xsessions \ - "$target"/usr/share/zoneinfo \ - "$target"/usr/share/{awk,man,doc,info,games,gdb,ghostscript,gnome,groff,icons,pixmaps,sounds,backgrounds,themes,X11} \ - "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} \ - "$target"/var/cache/yum \ - "$target"/var/lib/rpm \ - "$target"/var/lib/yum \ - "$target"/var/log/yum.log - -# ldconfig -rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig -mkdir -p --mode=0755 "$target"/var/cache/ldconfig diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index db23b24e465f8..cd25b9792a000 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -492,8 +492,7 @@ private static void verifyOssInstallation(Installation es) { // nc is useful for checking network issues // zip/unzip are installed to help users who are working with certificates. - // pigz is useful for compressing large heap dumps more quickly than gzip. - Stream.of("zip", "unzip", "pigz", "nc") + Stream.of("nc", "unzip", "zip") .forEach( cliBinary -> assertTrue( cliBinary + " ought to be available.", From c75980b20dd7947e56c941fbb2abf42ed800702e Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Fri, 26 Jun 2020 16:00:00 +0100 Subject: [PATCH 40/41] Trim dependencies further --- distribution/docker/src/docker/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index 6b0c6476fac89..ec918e6441944 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -76,8 +76,7 @@ RUN mkdir -m 755 /rootfs/dev && \\ RUN yum --installroot=/rootfs --releasever=/ --setopt=tsflags=nodocs \\ --setopt=group_package_types=mandatory -y \\ --skip-broken \\ - install \\ - basesystem bash glibc libstdc++ zip zlib + install basesystem bash zip zlib # The tini GitHub page gives instructions for verifying the binary using # gpg, but the keyservers are slow to return the key and this can fail the From 5b9e929af5b945462f2f69083769431d1e50796e Mon Sep 17 00:00:00 2001 From: Rory Hunter Date: Thu, 10 Sep 2020 12:47:21 +0100 Subject: [PATCH 41/41] Tweaks --- distribution/docker/build.gradle | 1 - distribution/docker/src/docker/Dockerfile | 5 ++- .../packaging/test/DockerTests.java | 39 +++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index b5daa09d30231..b54cb91c25450 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -224,7 +224,6 @@ void addBuildDockerImage(Architecture architecture, boolean oss, DockerBase base onlyIf { Architecture.current() == architecture } TaskProvider copyContextTask = tasks.named(taskName("copy", architecture, oss, base, "DockerContext")) dependsOn(copyContextTask) - dockerContext.fileProvider(copyContextTask.map { it.destinationDir }) String version = VersionProperties.elasticsearch diff --git a/distribution/docker/src/docker/Dockerfile b/distribution/docker/src/docker/Dockerfile index f57602a9aa0b8..c0c98651ca7f6 100644 --- a/distribution/docker/src/docker/Dockerfile +++ b/distribution/docker/src/docker/Dockerfile @@ -16,6 +16,7 @@ */ %> <% if (docker_base == "ubi") { %> ################################################################################ +# Build stage 0 `builder`: # Extract Elasticsearch artifact ################################################################################ @@ -203,7 +204,9 @@ RUN chmod 0660 config/elasticsearch.yml config/log4j2.properties <% if (docker_base == "ubi") { %> ################################################################################ -# Copy Elasticsearch distribution from previous stage +# Build stage 1 (the actual Elasticsearch image): +# +# Copy elasticsearch from stage 0 # Add entrypoint ################################################################################ diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java index 2c346f77fa550..d38854f95823b 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/DockerTests.java @@ -19,6 +19,25 @@ package org.elasticsearch.packaging.test; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.http.client.fluent.Request; +import org.elasticsearch.packaging.util.Distribution; +import org.elasticsearch.packaging.util.Installation; +import org.elasticsearch.packaging.util.Platforms; +import org.elasticsearch.packaging.util.ProcessInfo; +import org.elasticsearch.packaging.util.ServerUtils; +import org.elasticsearch.packaging.util.Shell.Result; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + import static java.nio.file.attribute.PosixFilePermissions.fromString; import static org.elasticsearch.packaging.util.Docker.chownWithPrivilegeEscalation; import static org.elasticsearch.packaging.util.Docker.copyFromContainer; @@ -51,26 +70,6 @@ import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.apache.http.client.fluent.Request; -import org.elasticsearch.packaging.util.Distribution; -import org.elasticsearch.packaging.util.Installation; -import org.elasticsearch.packaging.util.Platforms; -import org.elasticsearch.packaging.util.ProcessInfo; -import org.elasticsearch.packaging.util.ServerUtils; -import org.elasticsearch.packaging.util.Shell.Result; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; - -import com.fasterxml.jackson.databind.JsonNode; - public class DockerTests extends PackagingTestCase { private Path tempDir;