侧边栏壁纸
博主头像
王一川博主等级

努力成为一个不会前端的全栈工程师

  • 累计撰写 70 篇文章
  • 累计创建 20 个标签
  • 累计收到 40 条评论

目 录CONTENT

文章目录

编译型语言的Docker镜像构建小技巧

王一川
2023-01-29 / 0 评论 / 0 点赞 / 1,471 阅读 / 1,481 字
温馨提示:
本文最后更新于 2023-01-29,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

最近公司需要将一个底层服务打包成docker镜像,作为征战docker一年的小白当然不能错过这次练手的好机会。简单介绍一下这个项目:该项目为一个纯restful风格的后端项目,后端由java开发、worker节点由python开发、管理员使用的命令行工具由rust开发。当完成这份工作后记录下了整个过程中的心得体会

从标题也可以看出,主要是针对编译型语言的docker镜像构建小技巧,下文则分别对java、rust构建过程中的总结

一、整体思路

对于构建docekr镜像的优化技巧无非就是漏洞足够少、体积足够小。对于项目代码或依赖的包的漏洞不在本次讨论的范围类,除去这类漏洞本文主要关心docker镜像基于的liunx内核漏洞,因此一个安全、无漏洞的linux基础镜像是很有必要,这里给几个我常用的基础镜像

镜像名 镜像大小 包管理
amazonlinux 194MB yum
alpine 5MB apk

amazonlinux是AWS提供的一个注重安全、稳定和高性能的执行环境来开发和运行云应用程序,完全免费且长期更新;alpine操作系统是一个面向安全的轻型Linux发行版,由非商业组织维护的,相比于其他Docker镜像,它的容量非常小,仅仅只有 5 MB 左右,且拥有非常友好的包管理机制。使用这类基础镜像构建出来的应用具备镜像下载速度加快,镜像安全性提高,主机之间的切换更方便,占用更少磁盘空间等。

对于镜像的漏洞则可以通过桌面版的Docker(Docker Desktop)更方便的查看,例如:

amazonlinux

image-20230129134246461

alpine

image-20230129134347337

而对于编译型语言开发的项目往往最终产物就是一个二进制文件,那么构建的思路则是将编译好的二进制文件放到相关的由alpine等镜像提供最基础依赖环境中即可,用到的就仅仅是docker的分层构建。下面则分别对java和rust构建的心得体会

二、构建 java 程序

公司java项目使用的包管理工具是maven,因此对于一个maven项目通常的构建流程就是:mvn package打包、java -jar运行,完成这两步最小依赖环境是java和maven,应用分层构建的思路第一步使用一个具备java和maven环境的基础镜像编译源代码,第二步拷贝第一步jar包到由alpine构建的jre镜像中运行。

很幸运阿里为我们提供了一个具备java和maven环境且使用阿里源的镜像,下载方式

docker pull registry.cn-hangzhou.aliyuncs.com/acs/maven:3-jdk-8

注:截止到文章发布,该镜像并没有支持jdk11已经更高的LTS版本

第一步编译源代码的Dockerfile如下

FROM registry.cn-hangzhou.aliyuncs.com/acs/maven:3-jdk-8 AS build-env

ENV MY_HOME=/app
RUN mkdir -p $MY_HOME
COPY . $MY_HOME

WORKDIR $MY_HOME/gateway

RUN ["/usr/local/bin/mvn-entrypoint.sh","mvn","package"]

第二步将需要拷贝第一步的jar

FROM openjdk:8-jre-alpine

# 更新内核
RUN apk update && apk upgrade

COPY --from=build-env /app/gateway/target/*.jar /gateway.jar

EXPOSE 1081

CMD ["java","-jar","/gateway.jar"]

注:

  1. apk update && apk upgrade主要是在构建过程中尝试更新一下内核,可能会存在一些比较新的漏洞,开源社区对这类漏洞已经提供了补丁,但镜像并没有及时更新发布,这就需要我们手动更新一下
  2. --from=build-env的作用就是分层构建的核心命令,只将我们需要的文件拷贝过来,最大化的减少镜像体积。同时复制过来的文件越少理论上漏洞越小

最终的Dockerfile文件如下

FROM registry.cn-hangzhou.aliyuncs.com/acs/maven:3-jdk-8 AS build-env

ENV MY_HOME=/app
RUN mkdir -p $MY_HOME
COPY . $MY_HOME

WORKDIR $MY_HOME/gateway

RUN ["/usr/local/bin/mvn-entrypoint.sh","mvn","package"]

####################################################################################################
## Final image
####################################################################################################
FROM openjdk:8-jre-alpine

# 更新内核
RUN apk update && apk upgrade

COPY --from=build-env /app/gateway/target/*.jar /gateway.jar

EXPOSE 1081

CMD ["java","-jar","/gateway.jar"]

构建完成后检查镜像漏洞信息

8f829ae39dbfd926079465ca9edd9b0c

其漏洞基本都是spring相关的漏洞不用管(提给后端同学让他们去修即可,大概率都是升级版本),且镜像的大小只有区区200MB不到

三、构建 rust 程序

rust程序的产物就是一个可执行的二进制程序,如果使用alpine来执行编译后的二进制程序大概率是报错的,因为alpine使用musl libc,其glibc在alpine不可用表现出来的现象就是缺少核心依赖,类比java就是没有把依赖打入jar中。因此rust在编译过程中需要使用x86_64-unknown-linux-musl以静态链接到rust程序中,而这部分和rust交叉编译是殊途同归的(在聊一聊我的第一个开源项目最后的交叉编译有涉及)。

FROM rust:latest as builder

RUN rustup target add x86_64-unknown-linux-musl

RUN apt update && apt upgrde && apt install -y musl-tools musl-dev

WORKDIR /

COPY . .

WORKDIR auth

RUN cargo build --target x86_64-unknown-linux-musl --release

####################################################################################################
## Final image
####################################################################################################
FROM alpine:latest

WORKDIR /auth

# Import from builder.
COPY --from=builder /auth/target/x86_64-unknown-linux-musl/release/auth ./

CMD ["echo","version:1.0.0 by superlab group"]

构建完成后检查镜像漏洞信息

ae9b93e39b6d6b9f0dca9b84c15a4af1

小而安全

0

评论区