资讯动态

Docker制作Oracle 19c镜像:环境一致性与一条命令起库

发布时间:2026/10/25 3:14:31 来源:尧图企业网站定制
一套跑了三年的老系统要迁到新测试环境最头疼的往往不是应用本身而是它屁股底下那套 Oracle 19c。装一遍要三四个钟头字符集、监听、SID、内存参数、内核参数每一项都可能是最后一次失败的现场。这三年里我在不同机器上装过不下十遍 Oracle 19c从裸机装到虚拟机装再从虚拟机搬到 Docker 镜像里踩过的坑能写满两个笔记本。这篇文章要讲的就是我最终定型的那套做法用 Dockerfile 把一个干净的 Oracle 19c 单实例数据库固化成镜像构建一次之后任何一台装了 Docker 的机器上都能用一行命令起一套一模一样的库。核心关键词是 Docker 制作 Oracle19C 镜像适合两类人看一类是被商业数据库环境一致性折磨的开发、测试和运维另一类是想学镜像构建思路、但被数据库也能装容器里这件事劝退的初中级工程师。下面全部是我实际跑通过的东西参数、目录、报错、绕法都按能直接抄的程度写。1. 整体设计与方案选型为什么是自己做而不是拉现成的1.1 从装一天库到一条命令起库的真实痛点先说说我为什么非要做这件事。裸机装 Oracle 19c 的流程是固定的改内核参数、建 oinstall 和 dba 组、建 oracle 用户、装一堆 32 位兼容库、解压安装包、跑 runInstaller、跑两个 root 脚本、跑 netca 配监听、跑 dbca 建库。每一步都对环境有隐含假设——主机名能不能解析、/tmp 够不够大、swap 有没有、共享内存段够不够。这些假设在物理机上一般成立换到虚拟机或者云主机上就时不时崩一个崩了还得从日志里往回找。最要命的是这套东西不可复现同一个安装包A 机器上装出来的库和 B 机器上装出来的库字符集可能不一样SGA 大小可能不一样监听端口可能不一样出了问题谁也说不清是环境问题还是代码问题。容器化要解决的就是这个不可复现。把整个安装过程写进 Dockerfile环境假设全部显式化装哪些包、建什么用户、目录在哪、参数是多少全部落在代码里。构建出来的镜像 digest 一致跑起来的库就是一致的。这不是为了时髦是为了让环境问题这四个字从排查清单里消失。1.2 现成镜像与自建镜像的取舍市面上确实有别人做好的数据库镜像我早期也用过但很快就放弃了原因有三个。第一是不确定性别人做的镜像里装了什么、打了什么补丁、初始密码策略是什么我只能在跑起来之后一点点探这在生产相关的场景里是不能接受的。第二是体积和启动逻辑不可控很多现成镜像为了通用性塞了 RMAN 备份工具、EM 组件、多语言包动辄十几个 G启动脚本还带一堆分支判断出问题的时候日志绕三圈才看得懂。第三是版本锁定我需要的是特定小版本 特定补丁组合现成镜像给不了。自建镜像的代价是要自己写 Dockerfile、自己调静默安装的响应文件第一次做大约要花一两天。但这个投入是一次性的之后每加一个补丁、每换一个字符集改几行文件重新构建就行。我个人的判断标准很朴素如果这套环境你要用超过三次就值得自己构建。用一次两次手工装反而快。1.3 分层思路把变化慢的和变化快的分开写 Dockerfile 之前先想清楚哪些东西应该在哪一层。我的分法是三层层内容变化频率建议基础层操作系统 系统依赖包 用户组极低单独构建可复用安装层解压安装包 静默安装软件 root 脚本低跟补丁走与基础层合并或紧随其后数据层建库、建表空间、初始化数据中放在镜像启动脚本或独立镜像这个分法的好处是构建缓存能吃到。改一行启动脚本不用重跑三十分钟的软件安装。我见过不少人把所有东西塞一个 RUN 里改个密码要等半小时纯属自找麻烦。但也要注意反面层数太多会让最终镜像体积膨胀因为每一层的中间文件都留着。解决办法是在同一个 RUN 里下载—安装—删除闭环让中间产物不落到独立层里。这个原则后面讲 Dockerfile 的时候会具体体现。2. 构建前的准备基础镜像、安装包与响应文件2.1 基础镜像和操作系统版本怎么定Oracle 19c 对操作系统的要求比较死官方支持列表里最省事的是 7 系的 Linux 发行版。我一般用一个干净的 7 系基础镜像比如centos:7如果你想换成别的 7 系发行版也完全可以只要 glibc 版本够、yum 源能用安装过程基本一致。别用 8 系Oracle 19c 的预检查会直接卡在 glibc 和 libnsl 的兼容性上绕起来非常难受。基础镜像选定之后第一件事是确认架构。Oracle 19c 的安装包是 x86_64 的如果你在 ARM 机器上构建解压出来直接跑不了。这一点在苹果芯片的笔记本上特别容易翻车构建能过启动报cannot execute binary file。我的建议是构建阶段就显式指定--platform linux/amd64让 Docker 走模拟层虽然慢一点但不会白等。还有个小细节基础镜像里默认的/etc/hosts只有一行 127.0.0.1Oracle 安装和监听配置都依赖主机名能正向解析。我在 Dockerfile 里会额外补一句把容器 hostname 写进 hosts或者干脆在安装阶段用一个固定的主机名。这个坑后面第 5 章会展开讲因为它是构建成功、启动失败最常见的原因之一。2.2 安装包的获取与目录规划Oracle 19c 的软件包是一个约 3G 的 zip解压后直接就是 ORACLE_HOME 的内容这一点和 11g 时代需要跑 OUI 解压不一样省了一步。目录我固定成这套ORACLE_BASE/opt/oracleORACLE_HOME/opt/oracle/product/19c/dbhome_1ORACLE_INVENTORY/opt/oracle/oraInventory数据文件目录/opt/oracle/oradata归档日志目录/opt/oracle/oradata/fast_recovery_area为什么把 base 和 home 分开而不是直接装到/u01因为镜像里目录结构越扁平越好/opt/oracle一个挂载点就能把数据、日志、软件全罩住运行的时候只要-v挂这一个目录容器删了数据还在。如果你把数据文件放在/u01、软件放在/opt就得挂两个卷管理上多一层心智负担。安装包不要直接放在构建上下文里用COPY因为那会额外生成一个 3G 的镜像层即便后面rm掉体积也降不下来。我的做法有两种一是构建时用curl或wget直接下载到/tmp并在同一个 RUN 里解压删除二是如果内网不方便下载就把它放在构建上下文里用ADD配合多阶段构建最后只COPY --from需要的目录。前者更干净后者更适合离线环境。2.3 响应文件里真正需要改的那几个参数19c 静默安装靠db_install.rsp。这个文件在解压后的$ORACLE_HOME/install/response/目录下几百行参数但真正必须改的只有七八个其余保持默认就行。我整理了一份最小改动清单参数建议值说明oracle.install.optionINSTALL_DB_SWONLY只装软件不建库建库交给 dbca方便二次定制UNIX_GROUP_NAMEoinstall与前面创建的组保持一致INVENTORY_LOCATION/opt/oracle/oraInventory清单目录装完可清理ORACLE_HOME/opt/oracle/product/19c/dbhome_1必须与解压目录完全一致ORACLE_BASE/opt/oracle上层目录oracle.install.db.InstallEditionEE企业版只用标准版功能可以改 SE2oracle.install.db.OSDBA_GROUPdba管理员组oracle.install.db.rootconfig.executeRootScriptfalse关键容器里不要让安装器自己跑 root 脚本最后一项特别重要。默认值是true安装器会尝试su root去执行root.sh在容器里没有 sudo、也没有交互式终端这一步会挂住然后整个安装超时回滚前面三十分钟白跑。设成false之后自己在 Dockerfile 里显式执行root.sh可控得多。另外SELECTED_LANGUAGES建议只留en装多语言会让 ORACLE_HOME 多出几百兆中文环境靠NLS_LANG变量控制就够了不需要装语言包。3. Dockerfile 逐段拆解从装依赖到软件落地3.1 系统依赖与用户权限的铺垫这一段是最枯燥但最不能省的地方。Oracle 19c 的预检查会挨个检查依赖包缺一个就报PRVF-7531之类的错报错信息还不告诉你到底缺哪个。我把需要装的包整理成一段固定的 yum 命令实测能过预检查FROM centos:7 ENV ORACLE_BASE/opt/oracle \ ORACLE_HOME/opt/oracle/product/19c/dbhome_1 \ ORACLE_SIDORCLCDB \ ORACLE_PDBORCLPDB1 \ NLS_LANGAMERICAN_AMERICA.AL32UTF8 \ LANGen_US.UTF-8 RUN yum install -y --setoptskip_missing_names_on_installFalse \ binutils compat-libcap1 compat-libstdc-33 \ gcc gcc-c glibc glibc-devel ksh libaio libaio-devel \ libgcc libstdc libstdc-devel libXi libXtst libXrender \ libX11 libXau libxcb make sysstat unzip wget net-tools \ which hostname tar procps-ng \ yum clean all \ rm -rf /var/cache/yum这里有几个地方值得多说两句。compat-libstdc-33是 32 位兼容库很多人觉得 x86_64 用不上但 Oracle 的预检查认它缺了会告警。ksh看着没用其实root.sh和dbca的部分内部脚本依赖它不装的话可能在很后面的步骤才炸。net-tools提供netstat和ifconfighostname提供主机名解析这两个在配监听的时候会被调用镜像最小化的时候特别容易漏。用户和组这一段RUN groupadd -g 54321 oinstall \ groupadd -g 54322 dba \ groupadd -g 54323 oper \ useradd -u 54321 -g oinstall -G dba,oper -m -d /home/oracle oracle \ mkdir -p /opt/oracle/product/19c/dbhome_1 \ /opt/oracle/oraInventory \ /opt/oracle/oradata \ chown -R oracle:oinstall /opt/oracle \ chmod -R 775 /opt/oracleUID 和 GID 用固定的 54321不要让它自增。原因是运行时如果用-v挂载了宿主机目录宿主机上对应的权限位也得是这个号自增的话每次构建出来的号不一样挂载就权限错乱。这是我在两个环境之间来回切的时候吃过亏的地方。3.2 静默安装执行与 root 脚本的处理解压和安装放在同一个 RUN 里中间产生的 zip 不落层RUN mkdir -p /tmp/dbinstall \ cd /tmp/dbinstall \ wget -q 安装包地址 -O db_home.zip \ unzip -q db_home.zip -d $ORACLE_HOME \ rm -f db_home.zip \ chown -R oracle:oinstall $ORACLE_HOME注意unzip -d $ORACLE_HOME这个写法19c 的包解压出来就直接是 home 的内容不要再套一层目录否则 ORACLE_HOME 里会多一个db_home子目录runInstaller会报找不到。这个坑我踩过一次那次对着目录看了半天才反应过来。之后切换到 oracle 用户跑静默安装。Dockerfile 里切换用户用USER oracle但记得把响应文件提前COPY进去并改好权限因为切换用户之后就没法再COPY了COPY db_install.rsp /tmp/db_install.rsp RUN chown oracle:oinstall /tmp/db_install.rsp USER oracle RUN echo inventory_loc/opt/oracle/oraInventory /etc/oraInst.loc \ echo inst_groupoinstall /etc/oraInst.loc RUN $ORACLE_HOME/runInstaller -silent \ -waitforcompletion \ -ignorePrereqFailure \ -responseFile /tmp/db_install.rsp \ -invPtrLoc /etc/oraInst.loc \ -J-DTEMPDIR/tmp \ rm -rf /tmp/db_install.rsp-ignorePrereqFailure这个参数值得解释一下。容器里默认没有 swap/dev/shm也可能不够大预检查会在这两项上报失败但实际运行是没问题的共享内存的问题我们会在运行参数里解决。如果不用这个参数安装器会拒绝继续并要求你手工确认。这是自建镜像时的常规做法不是偷懒因为预检查的阈值是按物理机场景定的容器场景下不适用。装完之后切回 root 跑两个 root 脚本USER root RUN /opt/oracle/oraInventory/orainstRoot.sh \ $ORACLE_HOME/root.sh \ rm -rf $ORACLE_HOME/install/make.log \ $ORACLE_HOME/inventory/backuproot.sh会创建/etc/oratab、给几个可执行文件设置 setuid 位、配置网络库路径。它在容器里偶尔会卡在是否继续的交互上实际上默认输入回车就行Dockerfile 里加个echo管道喂进去更稳。3.3 镜像瘦身把能删的都删掉Oracle 装完ORACLE_HOME 通常有 10G 以上。这个体积直接推镜像仓库很难受我一般能压到 6G 左右。可删的东西不多但每一项都挺可观路径内容大约体积$ORACLE_HOME/OPatch补丁工具如果不需要后续打补丁数百 MB$ORACLE_HOME/.patch_storage补丁回滚信息视补丁而定$ORACLE_HOME/inventory安装清单备份数十 MB$ORACLE_HOME/jdk自带 JDKEM 不用可以删数百 MB$ORACLE_HOME/lib/*.a静态库开发静态库数百 MB/tmp/*安装临时文件视情况这里有个取舍要讲清楚删 jdk 和 OPatch 之后就没法在线打补丁了如果你的场景需要定期升级 RU就得留着。我的做法是分两个 tag一个 slim 版本给开发测试用一个 full 版本给需要维护的库用。另外 REM 和 EM 组件如果建库时选了-emConfiguration NONE相关的目录本身就不会生成太多不用特意处理。4. 完整实操流程从构建到跑起来4.1 构建命令与缓存利用构建命令本身没什么花样但参数里有几个直接影响结果的docker build \ --platform linux/amd64 \ --build-arg DB_ZIPdb_home.zip \ -t oracle19c:19.3.0-base \ -f Dockerfile .--platform linux/amd64前面说过防 ARM 踩空。-t里带上小版本号因为后面可能要出 19.10、19.20 的增量版本tag 不带版本号会乱。我习惯给镜像打两个 tag一个是精确版本19.3.0-base一个是滚动 tag19c-base后者方便脚本里引用。构建时间上第一次全量大约 25 到 40 分钟取决于机器性能和网络。如果你把依赖安装和数据安装分成了两层改启动脚本的时候只会重跑最后一层十几秒就完事。判断某一层会不会命中缓存看构建日志里是CACHED还是重新执行就行。有一点要注意RUN里的wget下载如果源地址内容变了缓存不会失效Docker 只按文本匹配缓存。所以补丁包之类的不要用固定的下载地址宁可在前面加一个ARG版本号让缓存可控地失效。构建完之后第一件事是docker history看各层大小找出异常膨胀的层。我见过一次因为yum install没加clean all缓存目录撑了 400 多兆加一句就没了。4.2 运行参数共享内存和内核参数的坑镜像有了跑起来的命令才是关键。最简版docker run -d \ --name ora19c \ --shm-size2g \ -p 1521:1521 \ -e ORACLE_SIDORCLCDB \ -e ORACLE_PDBORCLPDB1 \ -v /data/oracle/oradata:/opt/oracle/oradata \ oracle19c:19.3.0-base--shm-size2g是重中之重不是可选项。Docker 默认的/dev/shm只有 64MBOracle 启动时如果用 AMM自动内存管理会直接抛ORA-00845: MEMORY_TARGET not supported on this system。这个报错对新手极不友好因为字面意思跟共享内存没关系。解决办法就两条要么加--shm-size要么在建库时用-memoryMgmtType auto_sga也就是回到 ASMM用SGA_TARGETPGA_AGGREGATE_TARGET两个参数分别管不碰/dev/shm。我在镜像里默认走 ASMM同时在运行文档里要求加--shm-size双保险。内核参数方面容器里能改的只有一部分。sysctl类的参数比如kernel.shmmax、kernel.sem可以通过--sysctl传但需要有相应的权限docker run -d \ --sysctl kernel.shmmax4294967296 \ --sysctl kernel.shmall1073741824 \ --sysctl kernel.sem250 32000 100 128 \ --sysctl fs.file-max6815744 \ --sysctl fs.aio-max-nr1048576 \ ...这些值不是随便填的。kernel.shmmax建议设为物理内存的一半或者至少大于SGA_TARGET否则数据库起不来会报ORA-27123。fs.aio-max-nr默认 65536Oracle 用异步 IO 的时候可能不够报ORA-27090调到 1048576 就稳了。fs.file-max是文件句柄上限容器规模大的时候不够会报ORA-27300。不过说实话大多数 Docker 宿主机上这些宿主的 sysctl 值本身就不小真正必须显式传的其实只有--shm-size。我建议先把最小参数跑通遇到具体报错再加对应的 sysctl不要一上来堆一大串出了问题反而不知道哪项是必要的。4.3 启动脚本把建库和监听固化进去镜像里只有软件库还没建。建库这一步可以放在构建阶段镜像更大但启动快也可以放在启动阶段镜像小首次启动慢。我选的是构建阶段建库理由很简单启动时间可控。dbca 建库要三到八分钟如果放在启动阶段每次docker run都要等这么久体验太差。放在构建阶段只在构建时慢一次。建库命令大致是这样dbca -silent -createDatabase \ -templateName General_Purpose.dbc \ -gdbname ${ORACLE_SID} \ -sid ${ORACLE_SID} \ -createAsContainerDatabase true \ -numberOfPDBs 1 \ -pdbName ${ORACLE_PDB} \ -sysPassword ${SYS_PWD} \ -systemPassword ${SYS_PWD} \ -pdbAdminPassword ${SYS_PWD} \ -characterSet AL32UTF8 \ -nationalCharacterSet AL16UTF16 \ -databaseType MULTIPURPOSE \ -memoryMgmtType auto_sga \ -totalMemory 2048 \ -storageType FS \ -datafileDestination /opt/oracle/oradata \ -emConfiguration NONE \ -ignorePreReqs几个参数值得留意。-createAsContainerDatabase true是 19c 的推荐做法多租户架构后面加 PDB 很方便一个容器里能挂好几个业务库。-characterSet AL32UTF8是硬性的别用 ZHS16GBKUTF8 在跨平台导数据的时候省太多事。-totalMemory 2048是给 ASMM 的初始值实际运行时会按容器内存自动调整但这个值在构建时必须给太小了 dbca 会失败太大了构建机内存不够。启动脚本entrypoint.sh的核心逻辑是这样的#!/bin/bash set -e # 监听配置用 0.0.0.0 避免主机名变化导致的问题 if [ -f $ORACLE_HOME/network/admin/listener.ora ]; then sed -i s/(HOST .*)/(HOST 0.0.0.0)/ $ORACLE_HOME/network/admin/listener.ora fi lsnrctl start # 启动数据库 sqlplus -s / as sysdba EOF whenever sqlerror exit failure startup mount alter database open; exit EOF # 等库就绪 for i in $(seq 1 60); do if echo select 1 from dual; | sqlplus -s / as sysdba | grep -q 1; then echo database is ready break fi sleep 2 done exec $(HOST 0.0.0.0)这一句是我踩坑之后加的。默认情况下listener.ora里写的是构建时的容器主机名换台机器跑主机名变了监听就起不来报TNS-12541: TNS:no listener或者ORA-00119。改成0.0.0.0之后监听绑定所有网卡主机名随便变都不影响。这个改动很小但能省掉一大半镜像换机器就起不来的问题。健康检查也值得单独做一个小脚本用HEALTHCHECK指令挂上HEALTHCHECK --interval30s --timeout10s --start-period120s --retries5 \ CMD sqlplus -s / as sysdba select 1 from dual; | grep -q 1 || exit 1--start-period120s是给数据库启动留的窗口期太短的话容器会被反复重启因为健康检查在库还没 ready 的时候就判失败了。5. 常见问题与排查技巧实录5.1 构建阶段最常卡住的三个地方第一个是静默安装跑到一半回滚日志停在 Checking operating system requirements。九成是依赖包或者内核参数预检查没过。别去看runInstaller的标准输出那上面只有进度条真正的报错在$ORACLE_BASE/oraInventory/logs/installActions*.log里grep -i fail\|error一下就能定位到具体是哪个检查项。我遇到最多的是libaio版本不对和 swap 为 0。第二个是root.sh卡住不动。它在容器里有时候会等一个交互确认。构建日志里看到它停在那儿超过三分钟就是这个问题。解决办法是在前面加echo -e \n管道或者干脆给root.sh加环境变量ORACLE_OWNER之类让它跳过询问。第三个是构建完镜像启动报 cannot execute binary file。这是架构不匹配AMD 机器上构建的镜像拿到 ARM 机器上跑或者反过来。构建时显式指定--platform就能避免运行的时候也带上--platform linux/amd64。5.2 运行阶段的高频故障与速查表运行阶段的问题主要集中在起不来和连不上两类。我把踩过的整理成一张表现象报错关键字根因处理方式数据库起不来ORA-00845/dev/shm太小加--shm-size2g或改用 ASMM数据库起不来ORA-27123shmmax小于 SGA传--sysctl kernel.shmmax数据库起不来ORA-27102内存不足降SGA_TARGET或加容器内存限制监听起不来TNS-12541listener.ora主机名不匹配改成HOST 0.0.0.0连不上ORA-12514服务名不对用ORCLCDB或ORCLPDB1而不是 SID连不上ORA-01034实例没启动sqlplus / as sysdba手工startup看报错异步 IO 报错ORA-27090aio-max-nr不够--sysctl fs.aio-max-nr1048576容器反复重启无健康检查窗口太短加--start-period启动极慢无数据文件在 overlay 上挂卷到宿主机这张表里最想强调的是ORA-12514和持久化。前者是因为很多人还在按 11g 的习惯写jdbc:oracle:thin:host:1521:ORCL19c 多租户架构下要写成//host:1521/ORCLPDB1用服务名而不是 SID。后者是数据目录必须挂到宿主机否则容器一删库就没了而且 overlay 文件系统的 IO 性能对数据库来说很差启动会慢好几倍。5.3 字符集、时区和导入导出的连带问题字符集的问题在镜像里其实已经解决了建库时固定AL32UTF8但导入老数据的时候仍然会翻车。原因是源库如果是 GBK导出的时候NLS_LANG没设对中文会变成问号或者乱码。我的做法是在导入前显式设一遍export NLS_LANGAMERICAN_AMERICA.AL32UTF8 impdp system/pwd//localhost:1521/ORCLPDB1 \ directoryDUMP_DIR \ dumpfilesource.dmp \ remap_schemaSRC:DST \ transformsegment_attributes:n时区也是个隐性问题。容器默认是 UTC业务代码里sysdate和current_date可能差八小时。两个解决办法一是在 Dockerfile 里设ENV TZAsia/Shanghai并把/etc/localtime软链过去二是在数据库层面设ALTER DATABASE SET TIME_ZONE08:00。我一般两个都做因为 Oracle 的时区一部分看数据库参数一部分看操作系统只改一边会出现 TIMESTAMP WITH TIME ZONE 和 DATE 混用的时候对不上。还有一个容易忽略的点目录对象DIRECTORY的路径是数据库内部的不是容器的。导入时如果你在impdp里写directory/tmp/dumpOracle 会去容器里的/tmp/dump找所以 dump 文件必须挂载到容器里对应的位置并且提前CREATE DIRECTORY指过去。这一点和裸机不一样裸机上是同一个文件系统容器里隔了一层挂载。5.4 镜像交付与版本管理的几个习惯镜像做出来只是开始怎么管理更关键。我现在固定几个习惯第一镜像 tag 一定要带三要素小版本 补丁号 字符集。比如oracle19c:19.3.0-psu0-al32utf8。看着啰嗦但半年后回来找一个特定组合的镜像靠 tag 能直接定位不用猜。第二Dockerfile 和响应文件全部进版本库包括那个db_install.rsp。有人觉得这是 Oracle 的东西不该进仓库但它是你定制化的一部分不进去下次构建出来的东西就跟现在不一样。第三镜像里不放密码。建库时的sysPassword用构建参数传入构建完不要在镜像里留痕迹。更稳的做法是建库阶段用一个临时密码启动脚本里通过环境变量在首次运行时改掉。虽然容器里的东西本身谈不上多安全但至少别把密码写死在层里因为docker history是能把构建参数翻出来的。第四留一份精简的构建文档在镜像里就放在/opt/oracle/README写清楚版本、字符集、SID、PDB 名、默认端口、需要哪些运行参数。这东西看着多余但半年后接手的人可能就是你自己会感谢当时的自己。我在实际使用中的体会是容器化一套商业数据库真正花时间的从来不是 Docker 本身而是把那些装完就忘的隐式步骤一个个挖出来写进文件。第一遍做的时候会觉得比手工装还累因为手工装错了可以手工修Dockerfile 里错了就得从头构建。但等第二套、第三套环境需要拉起来的时候一条docker run就完事那种感觉是完全不一样的。我现在给这套镜像留了个扩展方向把建库参数和初始化脚本抽成环境变量驱动的入口让同一个镜像既能起空库也能起带初始数据的库这样测试环境和演示环境就能复用同一个基础镜像只在启动时传不同的初始化脚本。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑