最近公司需要将一个底层服务打包成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
alpine
而对于编译型语言开发的项目往往最终产物就是一个二进制文件,那么构建的思路则是将编译好的二进制文件放到相关的由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"]
注:
apk update && apk upgrade
主要是在构建过程中尝试更新一下内核,可能会存在一些比较新的漏洞,开源社区对这类漏洞已经提供了补丁,但镜像并没有及时更新发布,这就需要我们手动更新一下--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"]
构建完成后检查镜像漏洞信息
其漏洞基本都是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"]
构建完成后检查镜像漏洞信息
小而安全