Docker極簡入門:使用Docker執行Java程式

AD_milk發表於2021-11-24

執行簡單的Java程式

先在當前目錄建立App.java檔案

public class App{
    public static void main(String[] args){
        String os = System.getProperty("os.name");
        String osArch = System.getProperty("os.arch");
        String osVersion = System.getProperty("os.version");

        System.out.println(os);
        System.out.println(osArch);
        System.out.println(osVersion);
    }
}

然後建立Dockerfile

## 設定基礎映象
FROM openjdk:8

## 設定進入容器時的工作目錄
WORKDIR /root/app
## 將本地目錄複製進容器目錄中
COPY App.java /root/app

## 映象製作時執行的命令
RUN javac App.java

## 容器啟動時執行的命令
ENTRYPOINT java App

準備工作做好之後在當前目錄輸入命令

docker build .

.是指明Dockerfile檔案在哪個路徑之下,因為我們是在當前路徑下建立的,所以只需要填寫.就好。

build完成之後執行命令:

docker images
## 你的輸出可能會像這樣
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
<none>       <none>    2045f43c5e88   6 hours ago   526MB

REPOSITORYTAG都為<none>,這是因為剛剛在編寫Dockerfile時沒有指定它們。

之後的段落裡會解決這個問題,對於這個簡單的專案,我們只需要IMAGE ID就夠了。

現在根據映象啟動容器,執行命令:

## 偷懶的話可以只打IMAGE ID的前三位
## 這個IMAGE ID要根據你實際build出來的映象進行修改
## 請務必執行前一條命令docker images, 找到對應的ID
docker run 2045f43c5e88

輸出如下:

Linux
amd64
5.4.72-microsoft-standard-WSL2

這段Java程式的作用就是輸出當前作業系統的環境,根據輸出可以知道博主是在WSL2上執行docker的。

FROM alpine
WORKDIR /root/app
COPY App.java /root/app

RUN apk add openjdk8
## 設定環境變數
ENV JAVA_HOME /usr/lib/jvm/java-1.8-openjdk
ENV PATH $PATH:$JAVA_HOME/bin

RUN javac App.java

ENTRYPOINT java App

為了便於區別兩次構建出的不同映象,我們給之前的映象打上tag

使用命令:

docker tag 2045 myapp:1.0

build時可以使用-t來為映象打tag

docker build . -t myapp:2.0

再次執行命令

docker images
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
myapp        1.0            2045f43c5e88   12 hours ago   526MB
myapp        2.0            0545999c0fc0   25 hours ago   131MB

可以看到兩個映象已經被分別打上了tag,不過值得注意的是tag為1.0的映象體積要比2.0的大,這是為什麼?

直接將openjdk作為基礎映象會包含所有的Java語言編譯工具和庫。

多階段構建映象

其實執行Java程式只需要jre就行,我們沒有必要使用jdk作為基礎映象。但把程式打包成jar包,然後再交給docker的方式太麻煩了。

有沒有辦法實現編譯、打包、執行一體化呢?

當然是有的,簡單修改一下Dockerfile就可以了

先基於Maven映象生成jar包,最後執行在jre映象中,同時刪除已經用不到的Maven映象

首先建立一個maven專案

這是我的pom.xml檔案

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- 這裡將影響jar包的名字 -->
  <groupId>org.example</groupId>
  <artifactId>demoapp</artifactId>
  <version>app</version>

  <name>demoapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>

    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>

        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
          <configuration>
            <archive>  
			  <!-- 指定mainClass,不指定可能導致jar包執行不成功 -->
              <manifest>
                <addClasspath>true</addClasspath>
                <useUniqueVersions>false</useUniqueVersions>
                <classpathPrefix>lib/</classpathPrefix>
                <mainClass>org.example.App</mainClass>
              </manifest>
            </archive>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

然後在專案的根目錄下建立Dockerfile檔案

## build stage
FROM maven:3.8.3-jdk-8 AS MAVEN_BUILD

WORKDIR /build/
# 把本地的pom.xml和src目錄複製到映象的/build目錄下
COPY pom.xml /build/
COPY src /build/src/
# 執行打包命令
RUN mvn package

## run s
FROM openjdk:8-jre-alpine
# 設定工作目錄在映象的 /app 目錄下
WORKDIR /app
# 將第一階段生成的jar包新增到第二階段的容器中
COPY --from=MAVEN_BUILD /build/target/demoapp-app.jar /app/
# 執行jar包
ENTRYPOINT java -jar demoapp-app.jar
REPOSITORY   TAG            IMAGE ID       CREATED             SIZE
<none>       <none>         770d75ab38d7   7 seconds ago    84.9MB

最後生成的映象大小要比之前的500MB小了很多

Dockerfile常用命令

命令 描述
FROM 基礎映象
MAINTAINER 維護者資訊
ADD 新增檔案到映象(自動解壓)
COPY 新增檔案到映象(不解壓)
USER 設定執行RUN指令的使用者
ENV 設定環境變數
RUN 映象製作時執行的命令
ENTRYPOINT 容器啟動時執行的命令(無法被覆蓋)
CMD 容器啟動時執行的命令(多條CMD只執行最後一條)
EXPOSE 宣告要開啟的埠(實際還是要docker run -p port1:port2 才行)
VOLUME 目錄對映
ONBUILD 構建時自動執行的命令

相關文章