sdkman / sdkman-cli

The SDKMAN! Command Line Interface
https://sdkman.io
Apache License 2.0
6.13k stars 630 forks source link

Bug: OpenJDK: Add Support for Alpine Linux #1133

Open nezartarbin opened 2 years ago

nezartarbin commented 2 years ago

Bug report

I am trying out sdkman in a (custom) container image based on alpine linux.
When I tried installing Java with sdkman, then trying to initialize those programs, I get no such file or directory error.

Specifically, when running java or java -version or /root/.sdkman/candidates/java/current/bin/java I get the error:

bash: /root/.sdkman/candidates/java/current/bin/java: No such file or directory

Similar error for running spring for spring CLI (which I also installed through sdkman):

/root/.sdkman/candidates/springboot/current/bin/spring: line 118: /root/.sdkman/candidates/java/current/bin/java: No such file or directory

when looking to see the file root/.sdkman/candidates/java/current/bin/java, I can see it does indeed exist and has execution permissions:

# ls -l root/.sdkman/candidates/java/current/bin/java
-rwxr-xr-x    1 root     root         16320 Aug 17 11:55 root/.sdkman/candidates/java/current/bin/java

To reproduce

The container image below:

FROM alpine:3.16

ARG JDK_VERSION="17.0.4.1"
ARG MAVEN_VERSION="3.8.6"

RUN apk update \
    && apk upgrade \
    && apk add --no-cache bash curl zip \
    && curl "https://get.sdkman.io" | bash \
    && bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && sdk install java $JDK_VERSION-tem && sdk install maven $MAVEN_VERSION && sdk install springboot"

CMD ["bash"]

System info

Hope I can get help with this please; thanks!

helpermethod commented 2 years ago

Hi @nezartarbin!

Have you tried running the container as an unprivileged user? SDKMAN! is not intended to be run as root.

nezartarbin commented 2 years ago

Hi @helpermethod !

Thanks for the advice. I tried doing this with a non-root user, but it does not work still. I followed similar steps to the above, but with a different containerfile.

FROM alpine:3.16

ARG JDK_VERSION="17.0.4.1"
ARG MAVEN_VERSION="3.8.6"

RUN apk update \
    && apk upgrade \
    && apk add --no-cache bash curl zip \
    && adduser -D oat

USER oat
SHELL ["/bin/bash", "-c"]

RUN curl "https://get.sdkman.io" | bash \
  && source $HOME/.sdkman/bin/sdkman-init.sh \
  && sdk install java $JDK_VERSION-tem \
  && sdk install maven $MAVEN_VERSION \
  && sdk install springboot

CMD ["bash"]

again, I:

nezartarbin commented 2 years ago

Does SDKMAN! have any hard dependency on glibc or GNU coreutils? I suppose this may be one reason that it would have issues with alpine linux, as it is unique as a linux distro in using musl instead of glibc ans busybox instead of coreutils.

helpermethod commented 2 years ago

Hi @nezartarbin,

thanks for providing the containerfile. It seems even with glibc installed the OpenJDKs provided by SDKMAN! don't work, but we are using Foojay's DiscoAPI, which provides special OpenJDK versions for Alpine.

I'll speak with the team to see how much effort this is (doesn't look too hard from a first glance).

rfelgent commented 1 year ago

any progress ?

I have the same problem, the installation via SDKMan succeeds but running a java program fails (with the errors already mentioned by @nezartarbin )

rfelgent commented 1 year ago

The problem might be related to https://gitlab.alpinelinux.org/alpine/aports/-/issues/12981

marc0der commented 1 year ago

@eddumelendez any ideas regarding this?

ascheman commented 1 year ago

I can reproduce the problem with Docker (for Desktop) on my MacOS 13 as well. Looks like the java binary is linked to a shared library which is missing some symbols:

$ ldd /root/.sdkman/candidates/java/current/bin/java
        /lib64/ld-linux-x86-64.so.2 (0x7f8439027000)
        libz.so.1 => /lib/libz.so.1 (0x7f8439008000)
        libjli.so => /root/.sdkman/candidates/java/current/bin/../lib/libjli.so (0x7f8438ff5000)
        libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7f8439027000)
        libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7f8439027000)
        libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f8439027000)
Error relocating /root/.sdkman/candidates/java/current/bin/../lib/libjli.so: __strdup: symbol not found
Error relocating /root/.sdkman/candidates/java/current/bin/../lib/libjli.so: __rawmemchr: symbol not found
ascheman commented 1 year ago

Where can I find more information, how the Temurin builds are provided for sdkman (on Alpine)?

@nezartarbin you probably want to make use of an official Temurin build for your Alpine JDK installation by following the instructions here: https://adoptium.net/en-GB/installation/linux/#_alpine_linux_instructions?

I only tested the java -version by now, and it works

# /usr/lib/jvm/java-17-temurin/bin/java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode, sharing)

ldd finds all libraries and does not complain over missing symbols if used on the real binary (not on /usr/bin/java which is a symbolic link):

# ldd /usr/lib/jvm/java-17-temurin/bin/java
        /lib/ld-musl-x86_64.so.1 (0x7f58f35f5000)
        libjli.so => /usr/lib/jvm/java-17-temurin/bin/../lib/libjli.so (0x7f58f35de000)
        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f58f35f5000)
        libz.so.1 => /lib/libz.so.1 (0x7f58f35c4000)

However, as @eddumelendez mentioned in the respective Slack discussion, the problem seems to be related to the usage of glibc vs. musl. The Temurin package as provided by Adoptium is using musl.

ascheman commented 1 year ago

Nevertheless, latest Temurin JDK builds were never (automatically) tested for the current Alpine releases in a clean way (only the edge version) -> adoptium/installer#590.

helpermethod commented 1 year ago

Currently there is no way for SDKMAN! to detect if the currently running Linux is an Alpine.

So as a first step, SDKMAN! would need to detect this, e.g. by checking the existence of /etc/alpine-release

[[ -f /etc/alpine-release ]]

Then we would need to pass this information to the SDKMAN! backend (the SDKMAN! candidates service), which then needs to incorporate this information to only return JDK versions which work for Alpine.

And we would also need to add support for musl-based JDKs to our Disco API importer.

Wdyt @eddumelendez @marc0der? I've probably forgotten a few steps :D.

nezartarbin commented 1 year ago

@helpermethod I think the problem might be related to Alpine's use of the musl C library instead of glibc. I would suggest checking for that instead, like maybe via checking if the output of ldd /bin/usr/env includes musl (via grep maybe?)

This way the fix would be generalized to other musl distributions

outsideMyBox commented 1 year ago

I have the same issue trying to install sdkman and Java on Alpine: For example, with the following Dockerfile:

FROM alpine:3.18.4

RUN apk update
RUN apk add bash curl zip

RUN addgroup -S my-group && adduser -S my-user -G my-group -s bash
USER my-user
WORKDIR /home/my-user

RUN curl -s "https://get.sdkman.io" | bash
RUN bash -c "export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$JAVA_HOME/lib/server" && source $HOME/.sdkman/bin/sdkman-init.sh"

The image is created via docker build -t sdkman-example .. When I run the container via docker container run --rm -it sdkman-example bash I get errors depending on the command I run:

>sdk list
# Ok, This returns a list of candidates
>sdk version
bash: /home/my-user/.sdkman/libexec/version: cannot execute: required file not found

From the previous comments I assumed it was related to the missing glibc. I then added in the dockerfile:

# https://wiki.alpinelinux.org/wiki/Running_glibc_programs
RUN apk add gcompat libstdc++

then I could get the version and install Java:

> sdk version
SDKMAN!
script: 5.18.2
native: 0.4.2
> sdk install  java  17.0.8-tem && sdk use java 17.0.8-tem
[...]
Using java version 17.0.8-tem in this shell. 

But this fails:

java --version
Error occurred during initialization of VM
Unable to load jimage library: /home/my-user/.sdkman/candidates/java/17.0.8-tem/lib/libjimage.so

I could make it work manually by setting the LD_LIBRARY_PATH :

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/lib/server
> java --version
openjdk 17.0.8 2023-07-18
[...]

I don't understand the missing path though and how to set it automatically.