首页 > 其他分享 >构建更好的Docker镜像的一些技巧

构建更好的Docker镜像的一些技巧

时间:2023-11-14 14:33:02浏览次数:26  
标签:Java 基础 构建 使用 镜像 Docker

现在,使用Docker或更复杂的K8S来部署你的服务应该是主流的选择了. 而这个做法的前提是使用把你的程序用docker打包构建成Docker镜像.

在这篇文章中, 我总结了我在构建Docker镜像积累的一些好的实践. 供大家参考与借鉴.

  1. 使用国内源

虽然国内这个情况令我们程序员觉得困扰. 但在国内做开发, 使用国内源基本是每个程序员的必备技能. 从npm国内源, Java Maven仓库国内源, 想要更好更快的编译我们的程序, 不使用国内源是非常浪费时间的行为.

同样,构建Docker镜像时,同样会面临这个问题. 特别是你在构建镜像中, 需要安装Linux的一些服务或软件时, 使用默认的官方源,会显著的让构建时间变得很长.

因此,在国内构建Docker镜像,在Dockerfile文件中,主动加上国内源的设置吧.

以我的一个go服务的构建来说明:

FROM golang:alpine AS build
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
RUN go env -w  GOPROXY=https://goproxy.io,direct
RUN apk update && apk add --no-cache git gcc build-base linux-headers

在这个构建中,我设置了两个国内源:

  • 针对alpine,设置了alpine国内源,加快alpine下安装软件的速度
  • 针对go, 设置了go的国内代理源, 显著加快go依赖的下载速度

如果在国内不使用国内源, 这个镜像的构建时间久的令人难以接受.

  1. 使用官方的镜像做为基础镜像

构建Docker镜像,最开始需要做的是选择一个基础镜像, 比如Java语言,需要JRE或是JDK; Node服务需要一个node环境等.

千万不要自己基于Linux来构建这些语言环境. 而是选择Docker官方提供的基础镜像. Docker提供了非常多的基础镜像, 这些都是久经验证的非常可靠的基础镜像.

在Docker Hub网站上去搜索一下就能找到这些优秀的基础镜像. 凡是Docker提供的基础镜像, 都是非常好认的, 就直接一个名称,没有隶属名.  比如golang这样的就是官方镜像. 而google/cloud-sdk这样的就不是官方镜像,而是Google提供的.

在这里,需要对Java做特别的说明,虽然官方提供了一个OpenJDK的基础镜像,但这个基础镜像已经不再维护了.  我现在用的都是eclipse-temurin这个Java基础镜像, 这也是Docker官方提供的.

  1. 使用统一的定制基础镜像

有时候你可能需要在基础镜像基础之上,添加一些定制的能力或功能. 而这个又是一种通用的需求. 比如你的一个Java微服务,可能要构建几十个镜像,每个镜像都有这些定制的功能.

这时候,为了避免不同服务或不同团队使用不同的基础镜像, 在公司或团队级别,使用统一镜像是更好的做法.

FROM my-company/base-java:17.0.3

比如,构建了一个基于eclipse-temurin的自己的定制基础镜像. 这样,所有需要构建的Java服务都使用这个定制基础镜像是最好的选择.

  1. 考虑更小的基础镜像

在合适的前提下, 你应该考虑使用更小的基础镜像.

比如,你在Linux系统上选择构建一个服务, 那其实你有很多基础Linux镜像可以选择,比如Debian, Ubuntu或是Alpine. 在合适的前提下, 选择Alpine是更好的.

但是, 需要注意的是, 除非它确实不影响你的服务,在其它要素不影响的前提下才这样做, 网上有一种docker镜像干啥都推荐用Alpine的主张,我并不赞同, 因为这里面有其它因素或影响. Alpine用的是***musl***而不是Linux主流的***libc***等,在选择时不能一概而论.

关于这个,我过往写过专门的文章,需要了解的可以参阅: 对Docker基础镜像的思考,该不该选择alpine

  1. 使用多平台构建

虽然服务器主流都是X64架构的, 但这并不是完全. ARM架构现在也越来越多的被使用,特别在国内, 统信主流是ARM而不是X64.

在构建你的镜像时,不要只考虑支持X64架构. 而应该考虑支持多平台, 构建一次,支持不同的架构是最佳实践. 事实上, 大多数编程语言都没有说只支持X64, 那你基于这些编程语言构建出来的东西,理论上也不会只依赖特定平台.

Docker的buildx是专门支持多平台的, 而在Docker Hub中,你只要稍等用心都会发现主流的镜像都是支持多平台的.

关于如何基于buildx构建多平台镜像,我写过专门的文章供参阅: Docker多平台镜像构建指引

  1. 利用多阶段构建

有时候,构建Docker镜像有一个很不好的问题,就是一些编译语言的依赖包下载. 比如Java中, 如果你不会多阶段构建,而又在镜像中编译项目的话,那每次都要下载maven或gradle中定义的那些依赖.

这个耗时非常久,而且浪费网络.

而针对这个困境, Docker特别提供了多阶段镜像. 多阶段构建大致就是指把一个Docker镜像构建分为多个阶段. 比如以上面的Java服务为便,利用多阶段构建你可以做成这样

  • 阶段一: 编译项目,这个过程会下载依赖
  • 阶段二: 构建真正的镜像

这样不同阶段的好处在于, 如果你的依赖定义文件没有发生变更的前提下, 阶段一的构建Docker会缓存,意味着下次在再构建时, 阶段一会直接跳过去,使用缓存.

这样就解决了前面的问题.

  1. 善用.dockerignore文件

如果你构建Docker镜像,都从来没有定义,甚至不知道.dockerignore的存在, 那就不应该了.

在构建Docker镜像的过程中, Docker会先将本地的一个目录加载到Context上下文中,你才能COPY等. 但是项目中的很多目录,比如java中的build目录, npm中的node_modules其实并不需要加载到Context中, 因为我们会在构建过程中重新编译生成这些目录或文件.

这时候,你可以利用.dockerignore文件来忽略这些目录. 它的使用方式与.gitignore大致类似.

不要觉得这个无所谓或多此一举, 如果你没有合理的设置, 它会影响镜像的构建时间,更不好的是极大的加大你镜像的大小.

  1. 不要使用root用户

我见过很多程序员或运维人员, 一直使用root用户来部署或运维Linux系统. 这是非常不专业的做法.

这个行为在docker镜像中也是存在的, 很多人构建Docker镜像, 完全没有意识到Docker镜像中也存在用户的概念. 没有对这个做任何处理, 这意味着你就是使用Root用户在运行这个镜像服务.

从安全上来说,这是非常不妥当的.

FROM eclipse-temurin:17.0.6_10-jre
RUN useradd -ms /bin/bash lingen
USER lingen

定义一个用户是非常简单的, 如上代码所示. 只要这样, 这个镜像运行时, 就是以你定义的用户来运行.

当然,在一些复杂的镜像构建中,要考虑用户权限,及后续挂载Host Volume时可能会有权限上的问题. 这一点后续我有时间再单独聊一下.

  1. 扫描你的镜像

镜像做好后,花点时间来分析与扫描一下你的镜像,也是非常有必要的. 在安全上这一步不可少.

大多数人可能都没有这个意识, 但安全非常重要.

而Docker官方其实提供了工具,也就是Docker Scout,专门干这个的. 稍微花点时间学习研究下如何使用这个工具,再利用它来优化与加固你的镜像, 是非常好的做法.

最后

上面这些点就是我在构建镜像时,会特别注意的一些点, 相比过往,Dccker确实方便很多. 善用Docker, 能极大的简化我们服务的部署与运维.

标签:Java,基础,构建,使用,镜像,Docker
From: https://blog.51cto.com/u_64214/8368822

相关文章

  • 使用Docker部署Rust web应用
    Rust是一种非常快速和安全的系统编程语言,Rust因其web框架的速度而备受关注。在这篇文章中,我们将看到如何使用docker部署Rustweb应用程序。创建项目使用cargo创建一个新项目:cargonew--binrust-docker-project在Cargo.toml文件中添加依赖项:[dependencies]axum="0.6.6"tokio=......
  • Docker 树莓派 mysql5.7
    创建my.cnf[mysqld]##server_id=1##binlog-ignore-db=mysql##log-bin=replicas-mysql-bin##binlog_cache_size=1M##binlog_format=mixed##expire_logs_days=7#设置比较表名和数据库名时忽略大小写lower_case_table_names=1max_allowed_packet=125Mmax_c......
  • NineData:通过一个SQL语句构建实时数仓
    随着企业数据量呈现出爆炸式增长,跨部门、跨应用、跨平台的数据交互需求越来越频繁,传统的数据查询方式已经难以满足这些需求。同时,不同数据库系统之间的数据格式、查询语言等都存在差异,直接进行跨库查询十分困难。原生跨库查询的局限性虽然MySQL、Oracle、PostgreSQL等数据库系......
  • docker异常unable to add return rule in DOCKER-ISOLATION-STAGE-1 chain
    docker重装启动异常 INFO[2021-03-09T15:06:20.839195000+08:00]Loadingcontainers:start.INFO[2021-03-09T15:06:20.885624800+08:00]stoppingeventstreamfollowinggracefulshutdownerror="<nil>"module=libcontainerdnamespace=mobyINFO[2021-......
  • windows 10 安装ubuntu+docker(不安装desktop docker)步骤
    windows10安装ubuntu+docker(不安装desktopdocker)步骤1.安装wsl2,ubuntu22.04参考网络文档2.powershell里运行wsl3.sudo-i4.curl-fsSLhttps://get.docker.com-oget-docker.sh5.shget-docker.sh6.servicedockerstart7.在2.1通过脚本安装docker时,会出现下面这个问......
  • 用了这么多年Docker,殊不知你还有这么多弯弯绕!
    大家好,我是哪吒。单体应用拆分成微服务后,能够实现快速开发迭代,但因为小服务太多,导致测试和部署的成本提高。单体应用中,将springboot工程打包成一个war包,然后部署在Linux服务器的Tomcat中就可以了。拆分成微服务后,修改一个需求,可能会涉及多个微服务,这个时候,被修改过的代码都需要重新......
  • Docker小用
    dockerrun-d--nameelasticsearch-p9200:9200-p9300:9300-e"discovery.type=single-node"-eES_JAVA_OPTS="-Xms64m-Xmx256m"elasticsearch......
  • docker 系列
    docker系列目录docker系列一.docker定义1nameSpnce命名空间2cgroup控制组3为什么使用容器二.docker安装三.docker初步使用命令3.1镜像相关3.2容器相关3.3第一个项目3.4存储卷挂载四.dockerfile编写4.1基础语法4.2简单使用一.docker定义1nameSpnce命名......
  • docker Nginx ssl 配置
     nginx版本:1.21.1证书获取阿里云:_xxxx.xxxxx.com.pem、_xxxx.xxxxx.key域名:xxxx.xxxxx.com一、ssl 443端口二、非443端口 nginx.confserver{#监听端口,切记,12000后面必须加ssllisten12000ssl;#域名server_namexxxx.x......
  • macOS 通过 docker 安装 redis 集群
    安装集群macOS通过docker来进行安装redis机群,解决开发环境临时使用的问题,完成本地redis集群环境搭建。安装步骤如下:查询主机ip信息,命令如下:ifconfig删除容器信息,如果步骤执行错误了,可以删除容器,然后重新执行第3步dockerrm-fredis-30001dockerrm-fredis-30002doc......