Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion .github/workflows/build-kafka.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,42 @@ permissions:
packages: write

jobs:
build:
java:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Login to Registry
uses: docker/login-action@v4
with:
username: "${{ github.repository_owner }}"
password: "${{ github.token }}"
registry: ghcr.io

- name: Extract environment
run: |-
solution/kafka_build_vars.sh >> $GITHUB_ENV

- name: Build and push java
uses: docker/build-push-action@v7
with:
push: true
context: ./solution/java
tags: "${{ env.JAVA_IMAGE }}:${{ env.JAVA_TAG }}-${{ env.JAVA_BUILD_TREE_HASH }}"
cache-from: |
type=registry,ref=${{ env.JAVA_IMAGE }}:${{ env.JAVA_TAG }}-${{ env.JAVA_BUILD_TREE_HASH }}
type=gha,scope=java-${{ env.JAVA_TAG }}
cache-to: type=gha,mode=max,scope=java-${{ env.JAVA_TAG }}

kafka:
runs-on: ubuntu-24.04
needs: java
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down Expand Up @@ -47,6 +81,7 @@ jobs:
push: true
context: ./solution/kafka
build-args: |-
BASE_IMAGE=${{ env.JAVA_IMAGE }}:${{ env.JAVA_TAG }}-${{ env.JAVA_BUILD_TREE_HASH }}
scala_version=${{ env.scala_version }}
kafka_version=${{ env.kafka_version }}
tags: "${{ env.KAFKA_IMAGE }}:${{ env.KAFKA_TAG }}-${{ env.BUILD_TREE_HASH }}"
Expand Down Expand Up @@ -74,6 +109,7 @@ jobs:

cruise-control:
runs-on: ubuntu-24.04
needs: java
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -100,6 +136,7 @@ jobs:
push: true
context: ./solution/cruise-control
build-args: |-
BASE_IMAGE=${{ env.JAVA_IMAGE }}:${{ env.JAVA_TAG }}-${{ env.JAVA_BUILD_TREE_HASH }}
CC_VERSION=${{ env.KAFKA_CRUISECONTROL_TAG }}
tags: "${{ env.KAFKA_CRUISECONTROL_IMAGE }}:${{ env.KAFKA_CRUISECONTROL_TAG }}-${{ env.CRUISECONTROL_BUILD_TREE_HASH }}"
cache-from: |
Expand All @@ -109,6 +146,7 @@ jobs:

zookeeper:
runs-on: ubuntu-24.04
needs: java
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -135,6 +173,7 @@ jobs:
push: true
context: ./solution/zookeeper
build-args: |-
BASE_IMAGE=${{ env.JAVA_IMAGE }}:${{ env.JAVA_TAG }}-${{ env.JAVA_BUILD_TREE_HASH }}
ZK_VERSION=${{ env.ZK_VERSION }}
ZK_OPERATOR_VERSION=${{ env.ZK_OPERATOR_VERSION }}
tags: "${{ env.ZOOKEEPER_IMAGE }}:${{ env.ZOOKEEPER_TAG }}-${{ env.ZOOKEEPER_BUILD_TREE_HASH }}"
Expand Down
7 changes: 4 additions & 3 deletions solution/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ function flatten_source_images()
{
source <( ${REPOSITORY_DIR}/solution/kafka_build_vars.sh )

yq eval '.* | select(.image) | (.sourceRegistry // "docker.io") + "/" + .image + ":" + .tag' deps.yaml |
yq eval '.* | select(.image) | select(.buildOnly != true) |
(.sourceRegistry // "docker.io") + "/" + .image + ":" + .tag' deps.yaml |
sed '/ghcr.io\/scality\/zenko\/kafka/ s/$/-'"${BUILD_TREE_HASH}"'/' |
sed '/ghcr.io\/scality\/zenko\/cruise-control/ s/$/-'"${CRUISECONTROL_BUILD_TREE_HASH}"'/' |
sed '/ghcr.io\/scality\/zenko\/zookeeper/ s/$/-'"${ZOOKEEPER_BUILD_TREE_HASH}"'/'
Expand All @@ -100,14 +101,14 @@ function zenko_operator_tag()

function dependencies_versions_env()
{
yq eval '.[] | select(.image) | .envsubst + "=" + .image | sub("_TAG=", "_IMAGE=")' deps.yaml
yq eval '.[] | select(.image) | select(.envsubst) | .envsubst + "=" + .image | sub("_TAG=", "_IMAGE=")' deps.yaml
yq eval '.[] | select(.dashboard) | .envsubst + "=" + .dashboard | sub("_TAG=.*/", "_DASHBOARD=")' deps.yaml
yq eval '.[] | select(.policy) | .envsubst + "=" + .policy | sub("_TAG=.*/", "_POLICY=")' deps.yaml
find ${REPOSITORY_DIR}/monitoring/ -mindepth 1 -maxdepth 1 -type d -print0 | while IFS= read -r -d '' folder ; do
local dashboard="${folder##*/}"
echo "$(tr a-z- A-Z_ <<< $dashboard)_DASHBOARD=${dashboard}-dashboard"
done
yq eval '.[] | select(.tag) | .envsubst + "=" + .tag' deps.yaml
yq eval '.[] | select(.tag) | select(.envsubst) | .envsubst + "=" + .tag' deps.yaml
echo ZENKO_VERSION_NAME=${VERSION_FULL}

source <( "${REPOSITORY_DIR}/solution/kafka_build_vars.sh" )
Expand Down
6 changes: 4 additions & 2 deletions solution/cruise-control/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
# invocation and jar layout come from:
# https://github.com/adobe/cruise-control/blob/master/docker/Dockerfile

ARG JDK_IMAGE=eclipse-temurin:21.0.12_8-jdk-noble
ARG BASE_IMAGE=ghcr.io/scality/zenko/java:21.0.12_8
ARG CC_REPO=https://github.com/adobe/cruise-control.git
ARG CC_VERSION=3.0.3-adbe-20260722
ARG CC_COMMIT=c668b8316df9260dc0865e3c3f2274ebc60c5ce8

# The JDK only: the build drives Gradle through the project's own wrapper.
FROM eclipse-temurin:21.0.12_8-jdk-noble AS build
FROM ${JDK_IMAGE} AS build

ARG CC_REPO
ARG CC_VERSION
Expand All @@ -41,7 +43,7 @@ RUN ./gradlew --console=plain --no-daemon clean jar copyDependantLibs && \
cruise-control/build/dependant-libs/cruise-control-metrics-reporter.jar

####################################################################################################
FROM eclipse-temurin:21.0.12_8-jre-noble
FROM ${BASE_IMAGE}

# CruiseControl reads webserver.ui.diskpath unconditionally, so the directory has to exist
# even though no web UI is shipped.
Expand Down
2 changes: 1 addition & 1 deletion solution/cruise-control/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CruiseControl Docker Image

Docker file for building [CruiseControl](https://github.com/adobe/cruise-control) from Adobe's fork,
running on Eclipse Temurin.
running on an Eclipse Temurin runtime trimmed with `jlink`.

We build it ourselves because the published `adobe/cruise-control` images ship Oracle JDK under the
No-Fee Terms and Conditions, which does not allow redistribution as part of a commercial product.
Expand Down
5 changes: 5 additions & 0 deletions solution/deps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ jmx-javaagent:
image: jmx-javaagent
tag: 1.5.0
envsubst: JMX_JAVAAGENT_TAG
java: # shared base for our java images; built, never deployed
sourceRegistry: ghcr.io/scality/zenko
image: java
tag: 21.0.12_8
buildOnly: true
kaf: # CLI Kafka client all-in-one
tag: 0.2.8
envsubst: KAF_VERSION
Expand Down
45 changes: 45 additions & 0 deletions solution/java/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# syntax=docker/dockerfile:1

# Shared Java runtime for our kafka, kafka-connect, cruise-control and zookeeper images.
#
# Having one base rather than repeating these stages per image matters for size: the runtime
# and the patched OS end up in layers all four images share, instead of four copies that only
# look identical. Measured across the three service images, this is the difference between
# saving 21MB and saving 135MB.

ARG JDK_IMAGE=eclipse-temurin:21.0.12_8-jdk-noble
ARG BASE_IMAGE=debian:trixie-20260824-slim

####################################################################################################
# Minimal Java runtime: ~100MB, against ~300MB for the full JDK.
#
# ALL-MODULE-PATH keeps every platform module. Netty, the JMX agent, SASL and TLS reach modules
# through reflection and ServiceLoader, which jdeps cannot see, so trimming the list trades ~25MB
# for failures that only appear at runtime. --release-info keeps the vendor metadata jlink drops.
FROM ${JDK_IMAGE} AS jre_build

RUN jlink --add-modules ALL-MODULE-PATH \
--strip-debug --no-man-pages --no-header-files --compress=zip-6 \
--release-info ${JAVA_HOME}/release \
--output /javaruntime

####################################################################################################
FROM ${BASE_IMAGE}

ENV JAVA_HOME=/opt/java/openjdk \
LANG=C.UTF-8
ENV PATH=${JAVA_HOME}/bin:${PATH}

COPY --from=jre_build /javaruntime ${JAVA_HOME}

# jlink does not generate a class data sharing archive, and the stock Temurin images ship one.
# Without it the JVM starts roughly 2.5x slower, since every run re-parses the class metadata
# that CDS would otherwise map straight into memory.
RUN ${JAVA_HOME}/bin/java -Xshare:dump

# Debian's base image is only rebuilt periodically, so patch it: we pin it, so nothing else will.
# curl is used by the operators' probes, ca-certificates by anything talking TLS.
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates curl && \
apt-get clean && rm -rf /var/lib/apt/lists/*
26 changes: 26 additions & 0 deletions solution/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Shared Java Base Image

Base image for our Java services: kafka, kafka-connect, cruise-control and zookeeper. It is built
and pushed, but never deployed on its own.

It carries an [Eclipse Temurin](https://adoptium.net) runtime trimmed with `jlink`, on a pinned
Debian base whose packages are upgraded at build time.

## Why a shared image rather than repeating the stages

The same stages inlined in each Dockerfile produce layers that only *look* identical: the `jlink`
output does dedupe, but each `apt` layer is built separately and does not. Across the three service
images that is the difference between saving 21MB and saving 135MB.

Pinning our own base also means nobody else patches it -- Temurin's images are rebuilt
continuously, ours are not -- hence the `apt-get upgrade`. Without it the images scanned three
critical and ten high CVEs worse than the Temurin-based ones they replace.

## Bumping

Update `java.tag` in `solution/deps.yaml` and `JDK_IMAGE` in the Dockerfile together; the tag
is the Temurin version so that the two stay legible side by side. `BASE_IMAGE` (Debian) is pinned to
a dated tag and wants refreshing periodically.

Every image that builds `FROM` this one folds `solution/java` into its own build tree hash, so
bumping this image republishes all of them under new tags.
6 changes: 4 additions & 2 deletions solution/kafka/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ARG BASE_IMAGE=ghcr.io/scality/zenko/java:21.0.12_8

FROM alpine:3.24.2 AS kafka_dist

ARG scala_version=2.13
Expand All @@ -22,7 +24,7 @@ RUN tar -xzf $kafka_distro
RUN rm -r kafka_$scala_version-$kafka_version/bin/windows

####################################################################################################
FROM eclipse-temurin:21.0.12_8-jre-noble
FROM ${BASE_IMAGE}

ARG scala_version=2.13
ARG kafka_version=3.9.1
Expand All @@ -34,7 +36,7 @@ ENV KAFKA_VERSION=$kafka_version \

ENV PATH=${PATH}:${KAFKA_HOME}/bin

RUN mkdir ${KAFKA_HOME} && apt-get update && apt-get install curl -y && apt-get clean
RUN mkdir ${KAFKA_HOME}

COPY --from=kafka_dist /var/tmp/kafka_$scala_version-$kafka_version ${KAFKA_HOME}
COPY log4j.properties ${KAFKA_HOME}/config/
Expand Down
4 changes: 2 additions & 2 deletions solution/kafka/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Apache Kafka Docker Image

Docker file for building docker image for [Apache Kafka](https://kafka.apache.org) from official
[Apache Kafka Distros](https://www.apache.org/dyn/closer.cgi?path=/kafka/), running on Eclipse
Temurin.
[Apache Kafka Distros](https://www.apache.org/dyn/closer.cgi?path=/kafka/), running on an Eclipse
Temurin runtime trimmed with `jlink`.
14 changes: 11 additions & 3 deletions solution/kafka_build_vars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,31 @@ get_image_from_deps() {
}

cd "${CURRENT_DIR}"

# Our java images all build FROM the java image, so its tree has to feed their tree hashes: otherwise
# a change to the base would republish different content under an already-published tag.
JAVA_TREE=$(git rev-parse HEAD:solution/java)

cat <<EOF
scala_version=$(yq eval '.kafka.tag | split("-").[0]' deps.yaml)
kafka_version=$(yq eval '.kafka.tag | split("-").[1]' deps.yaml)
JAVA_IMAGE=$(get_image_from_deps java)
JAVA_TAG=$(yq eval '.java.tag' deps.yaml)
JAVA_BUILD_TREE_HASH=${JAVA_TREE}
KAFKA_IMAGE=$(get_image_from_deps kafka)
KAFKA_TAG=$(yq eval '.kafka.tag' deps.yaml)
KAFKA_CONNECT_IMAGE=$(get_image_from_deps kafka-connect)
KAFKA_CONNECT_TAG=$(yq eval '.kafka-connect.tag' deps.yaml)
JMX_JAVAAGENT_IMAGE=$(get_image_from_deps jmx-javaagent)
JMX_JAVAAGENT_TAG=$(yq eval '.jmx-javaagent.tag' deps.yaml)
MONGODB_CONNECTOR_TAG=$(yq eval '.mongodb-connector.tag' deps.yaml)
BUILD_TREE_HASH=$(git rev-parse HEAD:solution/kafka HEAD:solution/kafka-connect | sha1sum | cut -d' ' -f1)
BUILD_TREE_HASH=$(git rev-parse HEAD:solution/kafka HEAD:solution/kafka-connect ${JAVA_TREE} | sha1sum | cut -d' ' -f1)
KAFKA_CRUISECONTROL_IMAGE=$(get_image_from_deps kafka-cruise-control)
KAFKA_CRUISECONTROL_TAG=$(yq eval '.kafka-cruise-control.tag' deps.yaml)
CRUISECONTROL_BUILD_TREE_HASH=$(git rev-parse HEAD:solution/cruise-control)
CRUISECONTROL_BUILD_TREE_HASH=$(git rev-parse HEAD:solution/cruise-control ${JAVA_TREE} | sha1sum | cut -d' ' -f1)
ZOOKEEPER_IMAGE=$(get_image_from_deps zookeeper)
ZOOKEEPER_TAG=$(yq eval '.zookeeper.tag' deps.yaml)
ZOOKEEPER_BUILD_TREE_HASH=$(git rev-parse HEAD:solution/zookeeper)
ZOOKEEPER_BUILD_TREE_HASH=$(git rev-parse HEAD:solution/zookeeper ${JAVA_TREE} | sha1sum | cut -d' ' -f1)
ZK_VERSION=$(yq eval '.zookeeper.tag | split("-").[0]' deps.yaml)
ZK_OPERATOR_VERSION=$(yq eval '.zookeeper.tag | sub("^[^-]*-"; "")' deps.yaml)
EOF
19 changes: 8 additions & 11 deletions solution/zookeeper/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# scripts on top:
# https://github.com/adobe/zookeeper-operator/tree/master/docker

ARG JDK_IMAGE=eclipse-temurin:21.0.12_8-jdk-noble
ARG BASE_IMAGE=ghcr.io/scality/zenko/java:21.0.12_8
ARG ZK_VERSION=3.8.4
ARG ZK_OPERATOR_REPO=https://github.com/adobe/zookeeper-operator.git
ARG ZK_OPERATOR_VERSION=0.2.15-adobe-20260818
Expand All @@ -37,7 +39,7 @@ RUN set -eux; \
####################################################################################################
# zu.jar, plus the operator's scripts. The JDK only here: the build drives Gradle
# through the project's own wrapper.
FROM eclipse-temurin:21.0.12_8-jdk-noble AS zu_build
FROM ${JDK_IMAGE} AS zu_build

ARG ZK_OPERATOR_REPO
ARG ZK_OPERATOR_VERSION
Expand All @@ -57,7 +59,7 @@ RUN ./gradlew --console=plain --no-daemon shadowJar

####################################################################################################
# runtime
FROM eclipse-temurin:21.0.12_8-jre-noble
FROM ${BASE_IMAGE}

ARG ZK_VERSION

Expand All @@ -74,26 +76,21 @@ ENV ZOO_CONF_DIR=/conf \
ZOO_STANDALONE_ENABLED=true \
ZOO_ADMINSERVER_ENABLED=true

# socat, hostname, curl and ps are used by the operator's zookeeper*.sh probe scripts; gosu
# only by docker-entrypoint.sh, which the operator bypasses by setting an explicit command.
# Upstream also installs netcat, but nothing here calls it.
# socat, hostname and ps are used by the operator's zookeeper*.sh probe scripts; gosu only by
# docker-entrypoint.sh, which the operator bypasses by setting an explicit command. The base
# image already brings ca-certificates and curl.
# Upstream also installs netcat and dnsutils, but nothing here calls them.
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \
curl \
dnsutils \
gosu \
hostname \
procps \
socat; \
rm -rf /var/lib/apt/lists/*; \
gosu nobody true

# Ubuntu ships a default `ubuntu` user on uid/gid 1000, which is the uid ZooKeeper runs as
# upstream. Reclaim it rather than picking another, so data directory ownership is unchanged.
RUN set -eux; \
userdel -r ubuntu; \
groupadd -r zookeeper --gid=1000; \
useradd -r -g zookeeper --uid=1000 zookeeper; \
mkdir -p "$ZOO_DATA_DIR" "$ZOO_DATA_LOG_DIR" "$ZOO_CONF_DIR" "$ZOO_LOG_DIR" /opt/libs; \
Expand Down
6 changes: 2 additions & 4 deletions solution/zookeeper/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Apache ZooKeeper Docker Image

Docker file for building [Apache ZooKeeper](https://zookeeper.apache.org) from the official
distribution, with the additions zookeeper-operator expects, running on Eclipse Temurin.
distribution, with the additions zookeeper-operator expects, running on an Eclipse Temurin runtime
trimmed with `jlink`.

We build it ourselves because `ghcr.io/adobe/zookeeper-operator/zookeeper` ships Oracle JDK under the
No-Fee Terms and Conditions, which does not allow redistribution as part of a commercial product.
Expand All @@ -13,9 +14,6 @@ The operator never uses the entrypoint: it sets the container command to
`/usr/local/bin/zookeeperStart.sh`, and its probes exec the other `zookeeper*.sh` scripts, which need
`socat`, `hostname`, `curl` and `ps`. The entrypoint is kept to match upstream for standalone use.

Ubuntu ships a default `ubuntu` user on uid/gid 1000, which is the uid ZooKeeper runs as upstream, so
it is deleted to free the uid. Moving ZooKeeper to another uid instead would change data ownership.

## Bumping

Update `zookeeper.tag` in `solution/deps.yaml` -- it is `<zookeeper version>-<operator version>`, and
Expand Down
Loading