无法使用sdkman构建dockerfile

3
我完全不了解Docker的概念。我正在创建以下Dockerfile作为练习。
FROM ubuntu:latest

MAINTAINER kesarling

RUN apt update && apt upgrade -y
RUN apt install nginx curl zip unzip -y
RUN apt install openjdk-14-jdk python3 python3-doc clang golang-go gcc g++ -y
RUN curl -s "https://get.sdkman.io" | bash
RUN bash /root/.sdkman/bin/sdkman-init.sh
RUN sdk version
RUN yes | bash -c 'sdk install kotlin'

CMD [ "echo","The development environment has now been fully setup with C, C++, JAVA, Python3, Go and Kotlin" ]

我正在使用SDKMAN!来安装Kotlin。最初的问题是,我使用的是RUN source /root/.sdkman/bin/sdkman-init.sh而不是RUN bash /root/.sdkman/bin/sdkman-init.sh。然而,它给出了错误,说source not found。因此,我尝试使用RUN . /root/.sdkman/bin/sdkman-init.sh,但它没有起作用。然而,RUN bash /root/.sdkman/bin/sdkman-init.sh似乎可以工作,因为它不会给出任何错误并尝试运行下一个命令。然而,docker随后会出现sdk: not found的错误提示。
我错在哪里了?
需要注意的是,这些步骤对于我的主机发行版(我正在运行docker的那个)非常有效,该发行版是Pop!_OS 20.04

sdk 安装在哪里?(也许您应该检查 PATH 的值。) - ynn
你为 sdk 做了配置吗? - ynn
@ynn,嗯...你看,我已经在Dockerfile中写下了与我的主机相同的步骤(好的,首先,我安装docker的机器被称为主机机器,就像我们对虚拟机一样),并且在我的主机上没有任何配置就可以正常工作。在Dockerfile中有必要配置sdkman吗? - kesarling He-Him
2
(1) 可以。您可以将主机系统称为“host”。即使在Docker中,这也是一个有效的名称。 (2) 不行。通常,模仿在主机上执行的相同步骤是不够的。请不要忘记,您正在设置不同的操作系统(ubuntu vs pop!_os),更重要的是,docker基础镜像(例如ubuntu:latest)通常是最小化的;许多配置或必要工具都会缺失。 - ynn
3个回答

13

实际上,脚本 /root/.sdkman/bin/sdkman-init.sh 会 source sdk

source 是 bash 内置的命令,而不是文件系统中某个二进制文件。

source 命令在当前 shell 中执行文件。

每个 RUN 指令都将在当前镜像层的顶部执行任何命令并提交结果。

生成的提交镜像将用于 Dockerfile 中的下一步操作。

尝试这样做:

FROM ubuntu:latest

MAINTAINER kesarling

RUN apt update && apt upgrade -y
RUN apt install nginx curl zip unzip -y
RUN apt install openjdk-14-jdk python3 python3-doc clang golang-go gcc g++ -y
RUN curl -s "https://get.sdkman.io" | bash
RUN /bin/bash -c "source /root/.sdkman/bin/sdkman-init.sh; sdk version; sdk install kotlin"

CMD [ "echo","The development environment has now been fully setup with C, C++, JAVA, Python3, Go and Kotlin" ]


我认为 "SHELL指令" 这个短语有歧义;bash也是一个(子)shell。我理解你想表达什么,但可能不太适合初学者。 - ynn
1
好的,我会解释。你尝试使用我的Dockerfile构建Docker镜像了吗? - G1Rao
谢谢。不好意思,由于安全原因,我不会编译未知的Dockerfile。(我对sdk一无所知。) - ynn
哦!你的意思是 RUN bash /root/.sdkman/bin/sdkman-init.shRUN sdk version 在两个不同的镜像中运行? - kesarling He-Him
是的,当然,这是图像的两个不同层,当然也是两个不同的外壳。 - G1Rao
@ynn,SDK是广为人知的,事实上,正如你所知,我们甚至有一个相关的标签 ;) 因此,安装它非常安全 :P - kesarling He-Him

8

在Ubuntu Dockerfile中使用SDKMAN

简述

  1. sdk 命令不是二进制文件,而是加载到内存中的bash脚本。
  2. Shell会话是一个"进程",这意味着环境变量和声明的Shell函数只存在于Shell会话存在的持续时间内;这个持续时间只有 RUN 命令执行期间那么长。
  3. 手动调整你的 PATH
RUN apt-get update && apt-get install curl bash unzip zip -y
RUN curl -s "https://get.sdkman.io" | bash

RUN source "$HOME/.sdkman/bin/sdkman-init.sh" \
    && sdk install java 8.0.275-amzn \
    && sdk install sbt 1.4.2 \
    && sdk install scala 2.12.12

ENV PATH=/root/.sdkman/candidates/java/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/scala/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/sbt/current/bin:$PATH

完整版

哇哦,这真是一次破解之旅。每行下面都有注释,说明为什么要运行某些命令。

我学到了很多关于unix、sdkman和docker的工作原理,以及为什么三者的交集会产生非常不寻常的行为。

# I am using a multi-stage build so I am just copying the built artifacts
# from this stage to keep final image small.
FROM ubuntu:latest as ScalaBuild

# Switch from `sh -c` to `bash -c` as the shell behind a `RUN` command.
SHELL ["/bin/bash", "-c"]

# Usual updates
RUN apt-get update && apt-get upgrade -y
# Dependencies for sdkman installation
RUN apt-get install curl bash unzip zip -y

#Install sdkman
RUN curl -s "https://get.sdkman.io" | bash

# FUN FACTS:
# 1) the `sdk` command is not a binary but a bash script loaded into memory
# 2) Shell sessions are a "process", which means environment variables
#    and declared shell function only exist for 
#    the duration that shell session exists
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" \
    && sdk install java 8.0.275-amzn \
    && sdk install sbt 1.4.2 \
    && sdk install scala 2.12.12

# Once the real binaries exist these are 
# the symlinked paths that need to exist on PATH
ENV PATH=/root/.sdkman/candidates/java/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/scala/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/sbt/current/bin:$PATH

# This is specific to running a minimal empty Scala project and packaging it
RUN touch build.sbt
RUN sbt compile
RUN sbt package


FROM alpine AS production

# setup production environment image here

COPY --from=ScalaBuild /root/target/scala-2.12/ $INSTALL_PATH

ENTRYPOINT ["java", "-cp", "$INSTALL_PATH", "your.main.classfile"]

嗯,那个方法并没有奏效,还是出现了同样的错误。只接受那个真正有效的答案。 - undefined

1
通常情况下,在Docker中,您希望避免使用“版本管理器”类型的工具;最好安装您需要的特定版本的编译器或运行时。
对于Kotlin而言,它是一个JVM应用程序以zip文件的形式分布, 因此安装起来应该相当容易:
FROM openjdk:15-slim
ARG KOTLIN_VERSION=1.3.72

# Get OS-level updates:
RUN apt-get update \
 && apt-get install --no-install-recommends --assume-yes \
      curl \
      unzip
# and if you need C/Python dependencies, those too

# Download and unpack Kotlin
RUN cd /opt \
 && curl -LO https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip \
 && unzip kotlin-compiler-${KOTLIN_VERSION}.zip \
 && rm kotlin-compiler-${KOTLIN_VERSION}.zip

# Add its directory to $PATH
ENV PATH=/opt/kotlinc/bin:$PATH

版本管理器的真正问题在于它们严重依赖于工具设置环境变量。正如@JeevanRao在他们的回答中所指出的那样,每个Dockerfile RUN命令都在单独的容器中的单独的shell中运行,并且该命令中的任何环境变量设置都会在下一个命令中丢失。
# Does absolutely nothing: environment variables do not stay set
RUN . /root/.sdkman/bin/sdkman-init.sh

由于一张镜像通常只包含一个应用程序及其运行时,因此您不需要更改使用的运行时或编译器版本的能力。我的 Dockerfile 示例将其作为 ARG 传递,因此您可以在 Dockerfile 中更改它,或者传递 docker build --build-arg KOTLIN_VERSION=... 选项以使用不同的版本。


嗯...我需要时间来分析和学习你建议的Dockerfile。正如我已经说过的,这只是我接触Docker的第二天。事实上,就在一周前,我甚至不知道有Docker这样的东西存在。但我一定会尝试你提出的答案。非常感谢 :) - kesarling He-Him

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接