SpringBoot微服务编写Dockerfile流程及问题汇总

背景

跟 Docker 磕了两天,将一个包含 N 个微服务的应用部署包改造,使其能够生成 Docker 镜像,并在 Docker 容器中运行。几年前玩过 Docker,隐约记得几个命令「Dockerfile 命令:黑卡饮料、山楂果费、哦SUV,机器学习」,项目中用不到,早忘光了。

开着 metaso,一路追问了两天,终于搞定了这个应用的 Dockerfile 编写,卡住的点:

  1. 多层级命令结构下,启动脚本中的 . 的相对路径问题,它相对的是 WORKDIR 而不是当前执行脚本路径。
  2. 如何查看容器中运行程序的 logs 日志?非 Docker 命令输出到控制台的日志,而是 SpringBoot 应用通过 logback 组件写入到文件系统的日志。
  3. SpringBoot 应用配置文件中的变量,如何通过 Docker 运行命令传递并接收?基于 Nacos Config 的应用,容器运行时需要传递 Nacos 的信息。
  4. 外部宿主机怎么访问容器中的 Web 应用?
  5. 包含验证码的应用使用了 sun.awt.X11FontManager 绘图异常,怎么调整 JDK镜像?
  6. 一个容器中启动 N 个微服务 VS 每个容器启动一个微服务?
  7. 保证容器运行时不出现 Exited 状态的关键语句 tail -f /dev/null

需求描述

我有一个应用目录 myApp,下面有一个 appA,包含 bin/start.sh bin/stop.sh,Dockerfile。appA 需要引用到 myApp 目录的文件,现在需要对这个目录打镜像包含父级目录,而Dockerfile 位于子目录 appA中,怎么实现呢?

向秘搜AI输入上面的提问,它梳理出应用的目录结构如下:

	myApp/├── commonJar├── bin/│   ├── startAll.sh│   └── stopAll.sh		├── appA/│   ├── bin/│   │   ├── start.sh│   │   └── stop.sh│   └── Dockerfile│   └── bootstrap.yml		│   └── appA.jar	├── appB/│   ├── bin/│   │   ├── start.sh│   │   └── stop.sh│   └── Dockerfile│   └── appB.jar├── appC/│   ├── bin/│   │   ├── start.sh│   │   └── stop.sh│   └── Dockerfile│   └── appC.jar		└── Dockerfile

要实现将 myApp 目录及其子目录 appA 打包成 Docker 镜像,并且 appA 中的 Dockerfile 需要引用 myApp 目录下的文件,可以按照以下步骤进行操作:

  1. 构建上下文:Docker 构建时需要一个构建上下文(context),这个上下文是一个目录,Dockerfile 中的所有 COPY 和 ADD 指令都会相对于这个目录来查找文件。因此,我们需要将 myApp 目录作为构建上下文。
  2. 指定 Dockerfile 路径:由于 Dockerfile 位于 appA 子目录中,我们需要使用 -f 参数来指定 Dockerfile 的路径。
  3. 编写 Dockerfile:在 Dockerfile 中,使用相对路径来引用 myApp 目录下的文件。

编写Dockerfile

单独为每个模块编写 Dockerfile,以模块 appA 为例,编写如下内容:

#use jdk
FROM eclipse-temurin:8-jre-alpine#work dir in docker.
RUN mkdir /opt/myApp
WORKDIR /opt#copy all content in myApp/* to /apps.
COPY . myApp#grant start.sh and stop.sh
RUN chmod +x /opt/myApp/appA/bin/start.sh#expose all ports for appA
EXPOSE 8080ENTRYPOINT ["/opt/myApp/appA/bin/start.sh"]
CMD ["in"]

调整 appA/bin/start.sh 启动脚本,之前定位 appA.jar 是通过相对路径,直接在 Linux 运行正常,使用 Docker 容器运行时,由于工作目录设置的是父级 myApp ,相对路径也是相对工作目录的,所以直接启动会出现 appA.jar 文件不存在。

此外,启动时由于服务内部使用日志框架将日志输出到 appA/logs 目录了,所以忽略了控制台日志,要想保证容器不退出,必须让启动脚本处于挂起状态,通过一个启动参数控制。

调整启动脚本如下:

#!/bin/sh
basePath=$(cd `dirname $0`; pwd)
echo "basepath is $basePath"#change dir to appA ,which is .. of start.sh path
cd $basePath/..# start appA.jar use nohup & and ignore console log
loadPath=../commonJar
nohup java -Xmx512m -Dloader.path=$loadPath -jar -Dlogging.config=./logback-spring.xml  appA.jar >/dev/null 2>&1 &#hold on if has parameter 
if [ -n "$1" ]; thenecho '$1 is not empty, holding on for container'tail -f /dev/null
fi

此外,应用的 Nacos 参数需要容器通过设置环境变量的方式接收,因此修改应用的配置文件 bootstrap.yml ,使用环境变量接收:

spring:cloud:nacos:# nacos 服务器地址server-addr: ${address}# nacos 配置中心config:enabled: trueusername: ${username}password: ${password}# 引用的配置文件所属的命名空间,public时必须注掉,非public可以放开并修改为目标名称namespace: ${namespace}

这就编写好了模块 appA 的 Dockerfile 文件了,进入根目录 myApp 下依次创建镜像、运行容器、停止容器、删除容器、删除镜像。启动 DockerDesktop,

第一步,进入应用根目录 cd /xxx/myApp

第二步,运行构建命令:docker build -t appa -f appA/Dockerfile . 。构建完成后,执行 docker images 查看镜像:
在这里插入图片描述
第三步,执行容器启动:docker run -d -e address=IP:port -e username=xxx -e password=xxx -e namespace=nonPublic -p 8080:8080 -v /Applications/dockerlogs:/opt/myApp/appA/logs --name appa appa
等待容器启动后,使用 docker ps -a 查看容器状态,正常是 Up :
在这里插入图片描述
第四步,停止容器:docker stop d8ec9fbf8f49
在这里插入图片描述

第五步,删除容器:docker rm d8ec9fbf8f49,只能针对 Exited 状态的容器进行删除。

第六步,删除镜像:docker rmi 616b785f371e,只能针对没有容器运行的镜像进行删除。

Docker 操作汇总

针对每个应用提供一个 docker 操作脚本,方便操作,汇总 docker 脚本如下

  1. 根目录下创建镜像:docker build -t myapp .,针对当前文件目录下的 Dockerfile进行编译,且镜像名称必须小写
  2. 在父级别目录中对子模块构建:docker build -t appa -f appA/Dockerfile .
  3. 后台进程方式运行容器:docker run -d -e address=IP:port -e username=xxx -e password=xxx -e namespace=nonPublic -p 8080:8080 -v /Applications/dockerlogs:/opt/myApp/appA/logs --name appa appa ,容器环境变量、挂载日志文件,开放宿主机端口和容器端口一致。
  4. 查看容器:docker ps -a
  5. 删除容器:docker rm containId
  6. 删除镜像:docker rmi imageId
  7. 查看镜像:docker images
  8. 查看日志:docker logs appa【容器名称】仅shell 执行时的控制台日志
  9. 针对运行状态的容器,可以打印容器中日志目录下的文件:docker exec -it containId /opt/myApp/appA/logs/a.log
  10. 存储镜像: docker save -o appA.tar appA:latest

启示录

第一点,纠结一个问题 「一个容器中启动 N 个微服务 VS 每个容器启动一个微服务?」根据 Docker容器的设计思想:

  1. 一次构建,到处运行"的可移植性
  2. 轻量高效的资源利用
  3. 微服务友好的架构哲学,单一职责原则:每个Container仅运行一个主进程(如Nginx/MySQL),通过组合多个Container完成复杂应用,天然契合微服务拆分。
  4. 开发即生产的生命周期管理
  5. 开放生态的扩展性

比较好的实践方式是一个微服务一个镜像,独立容器启动。那么之前的部署方式一台服务器上三个服务运行的方式就不可行了。但是如果硬是这么实践,也是没问题的吧,毕竟通过瘦身包方式部署的,微服务共用了通用 jar 包。

一个容器里面运行 N 个微服务的 Dockerfile 文件就编写在根目录 myApp 中,然后逐次调用子模块的 start.sh 脚本进行启动,只用对根目录的应用打一个镜像就可以了。小公司的单体多模块的应用,就没必要打 N 个镜像了。

但是需要注意在一个容器中同时启动的微服务的个数,如果过多的话,可能 Docker 容器运行有资源约束,可能有些服务启动会失败。

第二点,JDK 选择上面,本来所有模块统一用轻量级 eclipse-temurin:8-jre-alpine 可以的,单有一个包含验证码的模块,这个精简镜像存在字体缺失问题,所以对该模块使用 openjdk:17-jdk 镜像。

最后一点,docker run 命令的参数 -v 必须在 --name 之前。用了 Docker 容器管理后,程序就不需要提供 stop 脚本了,直接通过容器的 stop 命令就可以停止应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/906528.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch语法学习

启动 python main.py --config llve.yml --path_y test -i output

基于LiveData和ViewModel的路线管理实现(带PopupWindow删除功能)

包含RecyclerView绑定、PopupWindow删除功能和SharedPreferences持久化存储。 1. RouteInfo类(实现Parcelable接口) java 复制 下载 import android.os.Parcel; import android.os.Parcelable;public class RouteInfo implements Parcelable {private Integer routeID;p…

jvm安全点(二)openjdk17 c++源码垃圾回收安全点信号函数处理线程阻塞

1. 信号处理与桩代码(Stub)​​ 当线程访问安全点轮询页(Polling Page)时: ​​触发 SIGSEGV 信号​​:访问只读的轮询页会引发 SIGSEGV 异常。​​信号处理函数​​:pd_hotspot_signal_handl…

如何用数据可视化提升你的决策力?

在数字化浪潮席卷全球的当下,数据已然成为企业和组织发展的核心资产。然而,单纯的数据堆积犹如未经雕琢的璞玉,难以直接为决策提供清晰有力的支持。数据可视化作为一种强大的工具,能够将海量、复杂的数据转化为直观、易懂的图形、…

VoiceFixer语音修复介绍与使用

一.简介 VoiceFixer 是一款基于深度学习的通用语音修复工具,主要用于恢复严重退化的语音信号,支持降噪、消除回声、提升音质等功能。 二.核心功能 1.语音修复与增强 VoiceFixer 采用端到端的神经网络模型,能够处理多种语音退化问题&#x…

Vue百日学习计划Day19-20天详细计划-Gemini版

重要提示: 番茄时钟: 每个番茄钟为25分钟学习,之后休息5分钟。每完成4个番茄钟,进行一次15-30分钟的长休息。动手实践: DevTools 的使用和 Git 命令的掌握都需要大量的实际操作。请务必边学边练。环境准备&#xff1a…

Qt初识.

认识 QLabel 类,能够在界面上显示字符串. 通过 setText 来设置的。参数 QString (Qt 中把 C 里的很多容器类,进行了重新封装。历史原因) 内存泄露 / 文件资源泄露对象树. Qt 中通过对象树,来统一的释放界面的控件对象. Qt 还是推荐使用 new 的…

WebGPU 图形计算

以下是关于 WebGPU 图形计算的基本知识点总结: 一、WebGPU 核心定位与优势 1. 与传统技术对比 维度WebGLWebGPU架构设计OpenGL ES 封装现代图形API抽象(Vulkan/Metal/D3D12)多线程支持单线程渲染多线程并行计算计算能力有限通用计算完整计算管线支持资源控制隐式状态管理显…

视觉基础模型

2.1 视觉的“大模型”时代:ViT的诞生与革新 在计算机视觉领域,卷积神经网络(CNN)曾是当之无愧的霸主。从LeNet到ResNet,CNN在图像分类、目标检测等任务上取得了巨大成功。然而,随着Transformer模型在自然语…

【React Native】快速入门

对于移动端应用来说,开发 Android 应用使用的语言有 java 和 kotlin,开发 ios 应用使用的语言有 obj-c 和 Swift 。因此,我们使用 react-native 编写一套代码进行跨端开发。 构建项目: npx create-expo-applatest安装 nativewin…

AR 开启昆虫学习新视界,解锁奇妙微观宇宙

在传统昆虫学习中,课堂教学是主要方式,老师通过板书、PPT 传授知识,但学生被动接受,书本静态图片无法展现昆虫真实比例、立体形态,学生难以直观感受复杂身体结构。博物馆的昆虫标本也是学习途径,不过标本放…

BI 大屏是什么意思?具体应用在哪些方面?

目录 一、BI 大屏的定义与内涵 1. 基本概念 2. 核心要素 3. 特点优势 二、如何搭建高效的 BI 大屏 1. 明确需求与目标 2. 选择合适的 BI大屏工具 3. 数据整合与清洗 4. 设计可视化界面 5. 持续优化与更新 三、BI 大屏在企业运营管理中的应用 1. 销售与营销领域 2.…

Kafka Go客户端--Sarama

Kafka Go客户端 在Go中里面有三个比较有名气的Go客户端。 Sarama:用户数量最多,早期这个项目是在Shopify下面,现在挪到了IBM下。segmentio/kafka-go:没啥大的缺点。confluent-kafka-go:需要启用cgo,跨平台问题比较多,交叉编译也…

Axure全链路交互设计:快速提升实现能力(基础交互+高级交互)

想让你的设计稿像真实App一样丝滑?本专栏带你玩转Axure交互,从选中高亮到动态面板骚操作,再到中继器表单花式交互,全程动图教学,一看就会! 本专栏系统讲解多个核心交互效果,是你的Axure交互急救…

自动化测试脚本点击运行后,打开Chrome很久??

亲爱的小伙伴们大家好。 小编最近刚换了电脑,这几天做自动化测试发现打开Chrome浏览器需要等待好长时间,起初还以为代码有问题,或者Chromedriver与Chrome不匹配造成的,但排查后发现并不是!! 在driver.py中…

现代人工智能系统的实用设计模式

关键要点 AI设计模式是为现代AI驱动的软件中常见问题提供的可复用解决方案,帮助团队避免重复造轮子。我们将其分为五类:提示与上下文(Prompting & Context)、负责任的AI(Responsible AI)、用户体验&…

经典面试题:TCP 三次握手、四次挥手详解

在网络通信的复杂架构里,“三次握手”与“四次挥手”仿若一座无形的桥梁,它们是连接客户端与服务器的关键纽带。这座“桥梁”不仅确保了连接的稳固建立,还保障了连接的有序结束,使得网络世界中的信息能够顺畅、准确地流动。 在面…

食品饮料行业AI转型趋势分析与智能化解决方案探索​

一、行业洞察:AI驱动食品饮料行业价值重构​ 当前,食品饮料行业正面临消费分级显性化、需求多元化与技术范式革新的三重挑战。根据《2024食品饮料行业全营销白皮书》,高收入群体倾向于高端化、个性化产品,而下沉市场更关注性价比…

Electron使用WebAssembly实现CRC-8 ITU校验

Electron使用WebAssembly实现CRC-8 ITU校验 将C/C语言代码,经由WebAssembly编译为库函数,可以在JS语言环境进行调用。这里介绍在Electron工具环境使用WebAssembly调用CRC-8 ITU格式校验的方式。 CRC-8 ITU校验函数WebAssembly源文件 C语言实现CRC-8 I…

python如何遍历postgresql所有的用户表

要遍历PostgreSQL数据库中的所有用户表,可以按照以下步骤操作: 安装必要依赖库 pip install psycopg2-binary使用标准SQL查询方案(推荐) import psycopg2def list_user_tables():try:conn psycopg2.connect(host"your_ho…