基于Alpine Linux镜像制作小体积JDK镜像

  为了提高镜像的上传和分发速度,我们通常会努力把自己的镜像做得尽量小!

  Java开发的jar包应用需要运行在JDK上(实际只需要JRE里的java运行工具和JVM,JDK中包含了JRE),而java运行工具和JVM又需要运行在操作系统上!而容器镜像正是这样从底层操作系统开始一层一层构建出来的,换句话说一个简单的Hello World镜像要能成功在容器中运行,它需要基于JDK镜像和操作系统镜像才能跑起来,JDK和操作系统都是非常大的。特别是操作系统,那么操作系统有没有小巧一点的呢?在Linux的众多发行版中还真有一款以小巧、简单和安全标榜的操作系统——Alpine Linux(https://www.alpinelinux.org/),下面是它的官网截图:

  它的发行包基本都在10M以下!如果以它为底层操作系统构建的镜像包应该可以大副度缩小体积!其实业界在大规模集群中基本都是用它做基础镜像的,在各大公开镜像仓库中基本都有它的镜像包!因此你可以不用自己制作操作系统这层的镜像了(其实基于它的JDK镜像包也很多了!),特别是Alpine Linux 3.10.0版本基本上都会有:

#将 alpine:3.10 镜像拉取到本地Docker仓库中备用 (这步拉不到的话,你可能需要换一下本地Docker的镜像源)
[root@master1 ~]# docker pull alpine:3.10 3.10: Pulling from library/alpine 3.10: Pulling from library/alpine 396c31837116: Already exists Digest: sha256:451eee8bedcb2f029756dc3e9d73bab0e7943c1ac55cff3a4861c52a0fdd3e98 Status: Downloaded newer image for alpine:3.10 docker.io/library/alpine:3.10

  接下来我们就基于该版本的alpine制作一下我们自己的JDK镜像:

  首先,准备一个制作JDK镜像所需文件的存放目录:

[root@master1 ~]# mkdir alpinejdk
[root@master1 ~]# cd alpinejdk/

  alpine能够如此小巧是因为它没有集成一些普通的Linux库,比如跑Java应用必须依赖的glibc相关的包,所以得像平常在Linux安装应用一样先为alpine安装这些包。主要需要的依赖包有 glibc、glibc-bin 和 glibc-i18n,这里我们选用 glibc 2.29-r0版本的。glibc是Git上的开源库,仓库地址为 https://github.com/sgerrand/alpine-pkg-glibc/releases,FQ上去选择你需要的版本下载就好(最好不要太新)!下面是我下载好的版本:

#将 glibc 相关包准备好备用
[root@master1 alpinejdk]# ls glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk

   然后,我们将需要的JDK也下载好,下面是我下载好的版本:

#将 JDK 包准备好备用
[root@master1 alpinejdk]# ls glibc-2.29-r0.apk  glibc-bin-2.29-r0.apk  glibc-i18n-2.29-r0.apk  jdk-8u201-linux-x64.tar.gz

  接着,我们在alpinejdk 目录创建一个Dockerfile文件(注意第一个字母D是大写),该文件是指导Docker制作镜像的,可以理解为Docker的输入参数(Dockerfile中的指令每执行一次都会在镜像上新建一层,过多无意义的层会造成镜像膨胀过大,所以能以 && 符号连接命令尽量连接,这样的命令执行后只会新建 1 层镜像):

#指明制作当前镜像将基于的基础镜像
FROM alpine:3.10
#指定所制作镜像的维护者和联系方式
MAINTAINER xurm "707669522@qq.com"
#指定容器运行时的默认工作目录(没有会自动创建),不指定则会使用FROM基础镜像指定的默认工作目录
WORKDIR /java
#从镜像构建上下文目录里添加需要的文件到容器镜像的指定目录(这里使用了与WORKDIR相同的目录,但不是必须这样),添加分ADD和COPY两种,ADD会复制并解压(这里添加的文件将影响镜像的大小,多文件COPY目的目录要以/结尾)
ADD jdk-8u201-linux-x64.tar.gz /java
COPY glibc* /java/
#更换安装过程中的工具包下载源(否则下载后面的工具会非常的慢)
RUN echo http://mirrors.aliyun.com/alpine/v3.10/main/ > /etc/apk/repositories && echo http://mirrors.aliyun.com/alpine/v3.10/community/ >> /etc/apk/repositories && apk update
#安装必要工具、下载alpine-pkgs.sgerrand公钥、安装必要的依赖
RUN apk --no-cache add ca-certificates wget && wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && apk add glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk
#清理缓存和容器镜像里不再要的包,保留JDK解压目录即可(没指定明确路径的都是相对于容器运行时的默认工作目录)
RUN rm -rf /var/cache/apk/* glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk
#设置时区(和普通操作系统一样需要对齐服务器时区,跑在上面的应用拿到的时间才不会乱)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
#配置环境变量
ENV JAVA_HOME=/java/jdk1.8.0_201
ENV CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$PATH:$JAVA_HOME/bin
#指定容器启动时需要执行的命令(需要的话就把注释#去掉)
#CMD ["java","-version"]

  编辑完Dockerfile并保存好后,使用以下命令构建镜像(Dockerfile文件所在的本地目录里所有文件和文件夹都将被发送到Docker守护进程的默认镜像构建上下文环境目录(类似于将整个文件夹从客户端发到了服务端备用),所以Dockerfile文件所在的本地目录里不要包含太多不必要的文件,否则会造成build过程在文件发送上花费太多不必要的时间):

#注意最后空格后有个点 . 
docker build -t alpinejdk:1.8.0_201 .

docker build的用法:docker build [OPTIONS] PATH | URL | -  ,我们上面将 [OPTIONS] 设为了 -t ,表示设置 “name:tag” 格式的镜像名称,最后的点 . 用于指定镜像构建过程中的上下文环境目录,点代表默认的镜像构建上下文环境目录。

整个构建过程如下所示:

[root@master1 alpinejdk]# docker build -t alpinejdk:1.8.0_201 .
Sending build context to Docker daemon  202.5MB
Step 1/12 : FROM alpine:3.10
 ---> e7b300aee9f9
Step 2/12 : MAINTAINER xurm "707669522@qq.com"
 ---> Using cache
 ---> b87841c6d9d5
Step 3/12 : WORKDIR /java
 ---> Using cache
 ---> 815b3585a13b
Step 4/12 : ADD jdk-8u201-linux-x64.tar.gz /java
 ---> Using cache
 ---> fe24f58834b8
Step 5/12 : COPY glibc* /java/
 ---> Using cache
 ---> 2d6ee49ba11b
Step 6/12 : RUN echo http://mirrors.aliyun.com/alpine/v3.10/main/ > /etc/apk/repositories && echo http://mirrors.aliyun.com/alpine/v3.10/community/ >> /etc/apk/repositories && apk update
 ---> Running in 13537d8736b0
fetch http://mirrors.aliyun.com/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.aliyun.com/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
v3.10.9-43-g3feb769ea3 [http://mirrors.aliyun.com/alpine/v3.10/main/]
v3.10.6-10-ged79a86de3 [http://mirrors.aliyun.com/alpine/v3.10/community/]
OK: 10344 distinct packages available
Removing intermediate container 13537d8736b0
 ---> bed4363bb0eb
Step 7/12 : RUN apk --no-cache add ca-certificates wget && wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && apk add glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk
 ---> Running in 86e9e0b943e5
fetch http://mirrors.aliyun.com/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.aliyun.com/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/2) Installing ca-certificates (20191127-r2)
(2/2) Installing wget (1.20.3-r0)
Executing busybox-1.30.1-r5.trigger
Executing ca-certificates-20191127-r2.trigger
OK: 7 MiB in 16 packages
(1/4) Installing glibc (2.29-r0)
(2/4) Installing libgcc (8.3.0-r0)
(3/4) Installing glibc-bin (2.29-r0)
(4/4) Installing glibc-i18n (2.29-r0)
Executing glibc-bin-2.29-r0.trigger
OK: 38 MiB in 20 packages
Removing intermediate container 86e9e0b943e5
 ---> 3600c6720ff9
Step 8/12 : RUN rm -rf /var/cache/apk/* glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk
 ---> Running in d028d3846b0a
Removing intermediate container d028d3846b0a
 ---> 6774a6316628
Step 9/12 : RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
 ---> Running in 740a35068e2a
Removing intermediate container 740a35068e2a
 ---> 878ee88270b7
Step 10/12 : ENV JAVA_HOME=/java/jdk1.8.0_201
 ---> Running in 74e6e8173bf1
Removing intermediate container 74e6e8173bf1
 ---> c2cbd5eb4061
Step 11/12 : ENV CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
 ---> Running in 94da1be12e1d
Removing intermediate container 94da1be12e1d
 ---> 0cae7e53648a
Step 12/12 : ENV PATH=$PATH:$JAVA_HOME/bin
 ---> Running in 382baec29fb7
Removing intermediate container 382baec29fb7
 ---> 6fa22163ea85
Successfully built 6fa22163ea85
Successfully tagged alpinejdk:1.8.0_201
[root@master1 alpinejdk]# 

 

  构建完后,我们查看下本地Docker镜像列表中是否有刚刚构建的镜像 alpinejdk:1.8.0_201 :

[root@master1 alpinejdk]# docker images | grep alpinejdk
alpinejdk                                                                1.8.0_201                      07a302bdf267        37 seconds ago      447MB

  看到有447MB,也是蛮大的,不过比起基于其他Linux镜像的就会小得多了!

  接着我们使用刚刚新制作的镜像创建并启动容器,再进入容器内部验证一下

# alpinejdkContainer 为自定义的容器名称,alpinejdk:1.8.0_201是启动容器使用的镜像,/bin/sh 是进入容器后执行的命令
[root@master1 alpinejdk]# docker run -it --name alpinejdkContainer alpinejdk:1.8.0_201 /bin/sh /java # java -version java version "1.8.0_201" Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode) /java # pwd /java/java # ls jdk1.8.0_201 /java #

  你也可以将上述步骤分开来做:

#使用镜像创建容器,会返回容器ID
[root@master1 alpinejdk]# docker create -it alpinejdk:1.8.0_201
cf3365896871e9f301374e97e02374789d06d512f37374434ef5037f4ac5b3ff
#使用容器ID启动容器,会返回启动后的容器ID [root@master1 alpinejdk]# docker start cf3365896871e9f301374e97e02374789d06d512f37374434ef5037f4ac5b3ff cf3365896871e9f301374e97e02374789d06d512f37374434ef5037f4ac5b3ff
#进入启动后的容器,进行验证 [root@master1 alpinejdk]# docker exec
-it cf3365896871e9f301374e97e02374789d06d512f37374434ef5037f4ac5b3ff /bin/sh /java # java -version java version "1.8.0_201" Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode) /java # pwd /java/java # ls jdk1.8.0_201 /java # ls jdk1.8.0_201/ COPYRIGHT bin man LICENSE include release README.html javafx-src.zip src.zip THIRDPARTYLICENSEREADME-JAVAFX.txt jre THIRDPARTYLICENSEREADME.txt lib /java #

  至此,我们就完成了基于Alpine Linux镜像制作小体积JDK镜像和基本验证的全部操作!以后可以基于该镜像制作jar包应用的镜像了!

     最后,我们还可以把制作好的镜像推送到远程仓库,方便K8S类的部署编排工具拉取进行部署,这里我以推到私有Harbor镜像库为例:

#确认Docker配置文件设置正确insecure-registries里指定的就是我的私有Harbor地址(定要带上端口号)
vi /etc/docker/daemon.json 
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn","https://hub-mirror.c.163.com","https://registry.aliyuncs.com","https://registry.docker-cn.com"],
  "insecure-registries": ["http://master1:80","http://192.168.17.3:80"],
  "log-driver":"json-file",
  "log-opts": {"max-size":"500m", "max-file":"3"}
}

#设置完了,重启一下docker
systemctl restart docker

#Docker重启完后登录私有Harbor仓库 [root@master1 harbor]# docker
login master1:80 Username: admin Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded #查看下本地的镜像 [root@master1 harbor]# docker images|grep alpinejdk alpinejdk 1.8.0_201 07a302bdf267 3 hours ago 447MB
#针对目标远程仓库重新打一个镜像tag ,命令格式:docker tag SOURCE_IMAGE[:TAG]
<目标仓库IP:Port>/<项目名>/<目标仓库>[:TAG] [root@master1 harbor]# docker tag alpinejdk:1.8.0_201 master1:80/library/alpinejdk:1.8.0_201
#重新查看下本地的镜像 [root@master1 harbor]# docker images
|grep alpinejdk alpinejdk 1.8.0_201 07a302bdf267 3 hours ago 447MB master1:80/library/alpinejdk 1.8.0_201 07a302bdf267 3 hours ago 447MB
#推送镜像 [root@master1 harbor]# docker push master1:
80/library/alpinejdk:1.8.0_201 The push refers to repository [master1:80/library/alpinejdk] 9dcb9e95b7af: Pushed de1df042d87a: Pushed cb44c39bbcd2: Pushed 0f3322bd9f41: Pushed a215608517ec: Pushed 40ccd5e2d8f7: Pushed 27079e540ba6: Pushed 40d017b1ac30: Pushed f178acc5111a: Pushed 1752e52a71e4: Pushed ce33ccaebbcf: Pushed 6d7f93e84db5: Pushed 89b217289643: Pushed c7cf904c85df: Pushed 9fb3aa2f8b80: Pushed 1.8.0_201: digest: sha256:780c8e1a7b7d85d414f3b000a744089afa21672ca1d5e392210f78f9bf41dd81 size: 3455 [root@master1 harbor]#

 

 

 

 

  

 

posted @ 2023-02-22 16:54  岁月已走远  阅读(968)  评论(0编辑  收藏  举报