-
Notifications
You must be signed in to change notification settings - Fork 254
feat(telegraf): Add distroless container based on scratch base-image #892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # Telegraf on scratch: the static (CGO_ENABLED=0) binary plus only the runtime | ||
| # files it needs — no shell, package manager, or OS userland. The alpine stage | ||
| # GPG-verifies the release and assembles the exact final image tree under | ||
| # /rootfs; the scratch stage is a single COPY of it. | ||
|
|
||
| ARG TELEGRAF_VERSION=1.39.1 | ||
|
|
||
| FROM alpine:3.23 AS fetch | ||
| ARG TELEGRAF_VERSION | ||
|
|
||
| RUN set -eux; \ | ||
| case "$(apk --print-arch)" in \ | ||
| x86_64) ARCH='amd64';; \ | ||
| aarch64) ARCH='arm64';; \ | ||
| *) echo "Unsupported architecture: $(apk --print-arch)" >&2; exit 1;; \ | ||
| esac; \ | ||
| apk add --no-cache ca-certificates tzdata wget gnupg tar; \ | ||
| update-ca-certificates; \ | ||
| mkdir -p ~/.gnupg; echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf; \ | ||
| gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 24C975CBA61A024EE1B631787C3D57159FC2F927; \ | ||
| base="telegraf-${TELEGRAF_VERSION}_linux_${ARCH}.tar.gz"; \ | ||
| wget --no-verbose "https://dl.influxdata.com/telegraf/releases/${base}"; \ | ||
| wget --no-verbose "https://dl.influxdata.com/telegraf/releases/${base}.asc"; \ | ||
| gpg --batch --verify "${base}.asc" "${base}"; \ | ||
| mkdir -p /src /rootfs/usr/bin /rootfs/etc/telegraf /rootfs/etc/ssl/certs /rootfs/usr/share; \ | ||
| tar -C /src -xzf "${base}"; \ | ||
| # Copy from the explicit `telegraf-<v>/` prefix: release tar roots vary (some | ||
| # add a leading ./), which makes a fixed --strip-components unreliable. | ||
| src="/src/telegraf-${TELEGRAF_VERSION}"; \ | ||
| cp -a "${src}/usr/bin/telegraf" /rootfs/usr/bin/telegraf; \ | ||
| cp -a "${src}/etc/telegraf/telegraf.conf" /rootfs/etc/telegraf/telegraf.conf; \ | ||
| cp -a "${src}/etc/telegraf/telegraf.d" /rootfs/etc/telegraf/telegraf.d; \ | ||
| cp /etc/ssl/certs/ca-certificates.crt /rootfs/etc/ssl/certs/ca-certificates.crt; \ | ||
| cp -a /usr/share/zoneinfo /rootfs/usr/share/zoneinfo; \ | ||
| # Resolve hostnames via /etc/hosts before DNS. | ||
| printf 'hosts: files dns\n' > /rootfs/etc/nsswitch.conf; \ | ||
| # Lets pure-Go os/user (CGO_ENABLED=0) map uid 65532 to a name; without it | ||
| # lookups like procstat's user tag fail silently. USER below sets the identity. | ||
| # We run as 65532 (the distroless "nonroot" uid), NOT 65534/nobody: 65534 is the | ||
| # kernel overflow uid — the id that unmapped user-namespace ids and NFS root-squash | ||
| # collapse to — so running as it would make telegraf indistinguishable from a | ||
| # failed-to-map process to security/monitoring tooling. 65532 sits outside that. | ||
| # overflow uid default 65534: https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html | ||
| # unmapped userns id -> overflow: https://man7.org/linux/man-pages/man7/user_namespaces.7.html | ||
| # distroless nonroot = 65532: https://github.com/GoogleContainerTools/distroless/blob/main/common/variables.bzl | ||
| # (nobody is kept in the map so 65534-owned files still resolve; we just never run as it.) | ||
| printf 'root:x:0:0:root:/root:/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin\nnonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin\n' > /rootfs/etc/passwd; \ | ||
| printf 'root:x:0:\nnobody:x:65534:\nnonroot:x:65532:\n' > /rootfs/etc/group; \ | ||
|
srebhan marked this conversation as resolved.
|
||
| # $HOME and a writable /tmp for 65532. We set ownership, not mode bits like | ||
| # 1777: buildah's COPY preserves a dir's ownership but not its mode. Numeric id, | ||
| # not "nonroot": this chown runs in the alpine stage, whose /etc/passwd has no | ||
| # nonroot user, so a BusyBox name lookup would fail ("unknown user/group") and | ||
| # abort under set -eux. A numeric id skips the passwd lookup entirely. | ||
| # busybox chown name lookup + err string (xget_uidgid; mirror linked from busybox.net/source.html): | ||
| # https://github.com/vda-linux/busybox_mirror/blob/ec0c5cc142f1f9ea57235df5d093fbe180ad9c7d/libpwdgrp/uidgid_get.c#L77-L80 | ||
| mkdir -p /rootfs/home/nonroot /rootfs/tmp; chown 65532:65532 /rootfs/home/nonroot /rootfs/tmp | ||
|
srebhan marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth a note in the image docs: /tmp and /home/nonroot are writable only by uid 65532, so runtimes that assign an arbitrary uid (OpenShift's default SCC, an explicit runAsUser) get no writable /tmp or HOME, unlike the siblings whose base images ship /tmp as 1777. Given the buildah mode-preservation caveat you documented, I think documenting the constraint is the right call rather than fighting it, but it should be written down where users will find it.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The question is really: How about for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. buildx does preserve it, mode and ownership both. I built the same shape the Dockerfile uses: Exporting the final image (buildx v0.25.0) gives: So the sticky bit survives, and since official-images publishes through BuildKit that is the path that matters here. Worth setting chmod 1777 on /rootfs/tmp: it takes effect under buildx, and under buildah it is a no-op that costs nothing. That also closes something I was going to raise separately. As it stands /tmp is writable only by uid 65532, so a runtime that assigns an arbitrary uid (OpenShift's default SCC, an explicit runAsUser) gets no writable /tmp, unlike the alpine and debian variants whose bases ship /tmp as 1777. With the mode set, that difference goes away rather than needing a caveat in the docs. I only verified the buildx half, so I can't speak to whether the buildah behavior in the comment is accurate. Either way the comment needs a reword, since as written it reads as "mode bits do not survive COPY" in general when it is specific to the builder we do not publish with. |
||
|
|
||
| FROM scratch | ||
|
|
||
| ARG TELEGRAF_VERSION | ||
| ENV TELEGRAF_VERSION=${TELEGRAF_VERSION} | ||
| LABEL org.opencontainers.image.title="telegraf" \ | ||
| org.opencontainers.image.description="Distroless Telegraf — static binary on scratch (no shell, no OS userland, non-root)" \ | ||
| org.opencontainers.image.version="${TELEGRAF_VERSION}" \ | ||
| org.opencontainers.image.source="https://github.com/influxdata/influxdata-docker" \ | ||
| org.opencontainers.image.base.name="scratch" \ | ||
| org.opencontainers.image.vendor="InfluxData Inc." \ | ||
| org.opencontainers.image.licenses="MIT" | ||
|
|
||
| # The whole rootfs was staged and GPG-verified in the fetch stage; one COPY | ||
| # brings it over. Ownership (incl. the 65532-owned /home/nonroot and /tmp) is | ||
| # preserved by docker, podman, and buildah alike. | ||
| COPY --from=fetch /rootfs/ / | ||
|
|
||
| ENV HOME=/home/nonroot | ||
|
|
||
| # Numeric, not a name: with runAsNonRoot=true the kubelet reads the image's USER | ||
| # field directly (never its /etc/passwd), so a named user fails admission | ||
| # ("cannot verify user is non-root"). 65532 is the distroless "nonroot" uid. | ||
| # https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kuberuntime/security_context_others.go#L50 | ||
| USER 65532:65532 | ||
|
|
||
| ENTRYPOINT ["/usr/bin/telegraf"] | ||
|
skartikey marked this conversation as resolved.
|
||
| CMD ["--config", "/etc/telegraf/telegraf.conf", "--config-directory", "/etc/telegraf/telegraf.d"] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would name the uid 65532 entry "telegraf" rather than "nonroot" (keeping the numeric USER and uid exactly as they are). The name is about to become a contract: procstat's user tag, anything else resolving the uid, and our docs all say "telegraf" in the other variants, and renaming after release is a visible behavior change. "nonroot" buys familiarity with the distroless ecosystem, but for a telegraf image I think parity with the siblings wins. Happy to be argued out of it, but let's decide now rather than after the tag exists. @srebhan please let me know you thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also prefer
telegrafif this doesn't collide with k8s conventions of "non-root".