☰
Oracle 19C Docker 镜像从构建到持久化部署
2026/10/26 14:10:59 网站建设 项目流程

1. 先把这件事的边界想清楚

1.1 为什么要自己做一个 Oracle 19C 的 Docker 镜像

真正动过手的人都知道,Docker 生态里最不缺的是 MySQL、PostgreSQL 的官方镜像,最缺的是 Oracle 数据库的镜像。数据库镜像仓库里能搜到的,大多是第三方打包的,版本杂、来源不明、出了问题没人兜底。而 Oracle 19C 又是很多存量业务系统绕不开的版本——大量老的 ERP、报表系统、中间件都认这个版本的驱动和 SQL 语法。于是"用 Docker 制作 Oracle 19C 镜像"这件事,就从"想试试"变成了"必须搞定"。

我自己第一次做这个镜像,是因为要给一个十几年前的老业务做迁移验证。目标很明确:本地开发机上能一键起一个 19C 实例,数据能持久化,脚本能自动初始化,证完就删,不污染宿主机。用物理机装一个 19C,光是装完再打快照就得小半天,还不说后面清不干净。容器化之后,这件事变成了"构建一次镜像,之后随时 docker run"。

这篇文章想解决的问题很具体:怎么从零开始,把一个能跑起来、能建库、能持久化、能被应用连上的 Oracle 19C 装进一个 Docker 镜像里。适合三类人看:一是手上有个老系统必须跑 19C、但不想在被窝里装数据库的后端;二是在做 CI/CD 流水线,需要把数据库当成一次性的测试依赖拉起来再销毁的工程同学;三是想搞清楚"数据库容器化"到底特殊在哪、为什么它不像 Redis 那样一条 run 命令就完事的技术好奇心玩家。

需要先明确一个现实:数据库镜像和普通应用镜像完全是两回事。Nginx 镜像里装的是几十兆的二进制,启动就是起个进程;Oracle 19C 的完整安装是几个 GB 的体量,安装过程要跑一大堆脚本、要算内核参数、要分配共享内存、要建一堆控制文件和数据文件。所以"制作镜像"这件事,重点不在docker build那几条指令,而在于安装过程能不能在非交互环境下跑通,以及启动时能不能自动完成建库。

1.2 三条技术路线的取舍

真要做这件事,摆在你面前的路其实只有三条,选错了会浪费好几天。

第一条是直接用官方提供的构建脚本。Oracle 在自己的代码仓库里维护了一套docker-images工程,里面有针对不同版本数据库的 Dockerfile 和构建脚本。你只需要把官方发布的安装介质(一个几 GB 的 zip)放进指定目录,跑一条脚本,镜像就出来了。这条路的好处是省心、经过官方验证、启动脚本、健康检查、初始化目录约定都是现成的;坏处是镜像体积大、构建时间长、对宿主机资源有硬性要求,而且脚本里做了很多"默认假设",想改点东西得先读懂它。

第二条是手工写 Dockerfile,从基础系统镜像开始,覆盖安装介质里的静默安装(silent install),自己建库、自己配监听。这条路灵活性最高,可以把镜像压到最小,可以定制字符集、内存参数、补丁级别;代价是你要对 Oracle 的安装细节足够熟悉,否则会卡在"安装程序报了一个看不懂的错误"上。

第三条是拿第三方打包好的镜像直接用。这条路最省事,但也最不可控:你不知道里面装了什么补丁、启动脚本改了什么、有没有后门、版本对不对得上。生产相关的验证一律不建议走这条路,学习玩玩还行。

我的建议是:第一次做,走第一条,先把"能用"跑通,建立对整体流程的直觉;等你知道了镜像里每一层是什么、启动脚本干了什么,再走第二条做定制和瘦身。下面两章分别讲这两条路,你可以按需跳读。

1.3 先把宿主机的账算清楚

动手之前,有几个数字必须先算明白,不然你会在构建到 80% 的时候被磁盘不足打断。

项目最低要求建议值为什么
磁盘可用空间40 GB80 GB 以上安装介质约 2.8 GB,镜像约 7 GB,构建中间层、数据文件、日志都要占空间
内存4 GB8 GB 以上数据库实例最小可跑 1 GB SGA,但加上系统开销和构建过程,4 GB 以下会非常痛苦
交换分区2 GB与内存等量建库阶段内存峰值高,swap 不足会直接导致建库失败
CPU 核心24静默安装大量是单线程,但构建和解压能并行

镜像体积这块要有心理预期:19C 的企业版完整安装,光$ORACLE_HOME目录就是 4~5 GB,加上基础系统镜像、安装临时文件,最终镜像在 7 GB 上下是正常的。如果你看到某个号称"19C 精简版 800 MB"的镜像,那基本可以肯定里面的组件被裁掉了,跑跑查询可以,跑生产相关的东西风险很大。

还有一点容易忽略:Docker 的默认存储驱动和数据目录位置。如果你的/var/lib/docker在根分区上,而根分区只有 50 GB,构建两个版本就满了。动手前先df -h看一眼,必要时把 Docker 的>cd OracleDatabase/SingleInstance/dockerfiles ./buildContainerImage.sh -v 19.3.0 -e

跑起来之后,你会看到脚本先检查 zip 是否存在、大小对不对、校验和是否匹配,然后开始docker build。整个过程在普通开发机上大概 15~40 分钟,取决于磁盘 I/O。机械硬盘会明显更慢,这不是玄学,是因为安装过程要往镜像层里写几万个文件。

2.3 构建过程里 Dockerfile 到底干了什么

读懂 Dockerfile 能帮你在出错时快速定位。19.3.0 的 Dockerfile 逻辑大致是这样一条链:

第一部分,基于一个精简的 Linux 基础镜像,安装必要工具(unzip、libaio、bc、net-tools等),创建oinstall和dba用户组与oracle用户,把内核参数和资源限制写进配置文件。这一步是"把 Oracle 安装的环境先摆好",包括kernel.shmmax、kernel.shmall、fs.file-max、net.ipv4.ip_local_port_range这些经典参数,以及oracle用户的nofile、nproc限制。

第二部分,把安装介质COPY进镜像并解压到$ORACLE_HOME。这里有个细节:19C 开始,安装介质解压出来不再是"安装程序",而是"已经安装好的软件家目录",只需要跑一次runInstaller -silent完成配置,而不是像 11g 那样完整地装一遍。这是 19C 容器化能这么干的前提。

第三部分,跑runInstaller -silent -waitforcompletion完成软件安装,然后以 root 身份执行$ORACLE_HOME/root.sh。这一步如果失败,镜像构建就在这里断掉,日志会留在构建输出里。

第四部分,把container-entrypoint.sh、createDB.sh、checkDBStatus.sh这些运行期脚本拷进去,声明VOLUME /opt/oracle/oradata,声明EXPOSE 1521 5500,设置HEALTHCHECK,最后ENTRYPOINT指向那个入口脚本。

把这几部分对上号,你就能理解为什么这个镜像启动慢:软件是构建时装好的,但数据库是启动时才建出来的。镜像里其实没有ORCLCDB这个库,只有一堆空目录。第一次docker run时,入口脚本发现数据目录是空的,才会去调用dbca建库。这就是为什么官方给的健康检查start-period设了整整 30 分钟——建库确实可能要跑十几分钟。

2.4 构建完成后先看一眼镜像本身

构建结束,docker images里会出现一个类似oracle/database:19.3.0-ee的镜像。这时候别急着 run,先做两件事。

第一件,看体积和分层:

docker history --no-trunc oracle/database:19.3.0-ee | head -20

你会看到体积最大的一层是"解压安装介质"那一层,通常 4 GB 以上。这一层没法压缩,因为它是实打实的文件。理解这一点很关键:后面所有关于"镜像瘦身"的努力,主要战场都在这一层之外——清理临时文件、去掉 install 目录、合并 RUN 指令。

第二件,进容器看看目录结构,确认ORACLE_HOME、ORACLE_BASE的位置对不对:

docker run --rm -it --entrypoint /bin/bash oracle/database:19.3.0-ee

进去之后ls /opt/oracle,能看到product/19c/dbhome_1说明软件装好了,看到oradata是空的说明库还没建。这一步验证的是"软件层面 OK",把启动期的问题和构建期的问题隔离开,后面排查能少走一半弯路。

3. 手工写 Dockerfile:把每一步都捏在自己手里

3.1 什么时候该走手工这条路

官方脚本能做出来的镜像,为什么还要手工做一个?我遇到的实际需求有三类。

一是要控制字符集和建库参数。官方脚本的建库脚本虽然支持通过环境变量传ORACLE_CHARACTERSET、ORACLE_SID、ORACLE_PDB,但有些更细的参数(比如dbca的 block size、national character set、redo 日志大小、TEMP 表空间配置)它没暴露出来,你要么改脚本,要么自己建。

二是要打补丁。业务系统要求某个特定补丁级别,官方镜像包不带,你得在构建过程里把补丁打进去。手工 Dockerfile 里加一个opatch apply的步骤就行。

三是要瘦身。官方镜像里有不少开发验证用不到的东西(某些可选组件、安装日志、示例 schema),手工构建可以按需裁掉,把 7 GB 压到 5 GB 以内,对 CI 环境拉镜像的速度影响很大。

手工构建的核心难点只有一个:把交互式的图形安装变成一条能在 Dockerfile 里跑完的非交互命令。这个靠响应文件(response file)解决。

3.2 Dockerfile 骨架与关键指令

下面这份骨架是我实际用过、跑通的版本,做了适当裁剪。它不是最优解,但它把每个阶段都拆开了,方便你出问题时定位。

FROM oraclelinux:7-slim ENV ORACLE_BASE=/opt/oracle \ ORACLE_HOME=/opt/oracle/product/19c/dbhome_1 \ ORACLE_SID=ORCLCDB \ PATH=/opt/oracle/product/19c/dbhome_1/bin:$PATH \ LD_LIBRARY_PATH=/opt/oracle/product/19c/dbhome_1/lib # 1. 装基础依赖 + preinstall 包(如果有可用 yum 源就用它,省事) RUN yum install -y oracle-database-preinstall-19c \ && yum install -y unzip vim tar net-tools which \ && yum clean all \ && rm -rf /var/cache/yum # 2. 准备目录和权限 RUN mkdir -p $ORACLE_BASE/product/19c \ && mkdir -p $ORACLE_BASE/oraInventory \ && mkdir -p $ORACLE_BASE/oradata \ && mkdir -p $ORACLE_BASE/install \ && chown -R oracle:oinstall $ORACLE_BASE \ && chmod -R 775 $ORACLE_BASE # 3. 解压安装介质到 ORACLE_HOME COPY --chown=oracle:oinstall LINUX.X64_193000_db_home.zip /tmp/db_home.zip USER oracle RUN cd $ORACLE_HOME && unzip -q /tmp/db_home.zip && rm -f /tmp/db_home.zip # 4. 静默安装软件 COPY --chown=oracle:oinstall db_install.rsp /tmp/db_install.rsp RUN $ORACLE_HOME/runInstaller -silent -waitforcompletion \ -responseFile /tmp/db_install.rsp \ -ignorePrereqFailure \ && rm -f /tmp/db_install.rsp # 5. root 脚本必须在 root 下跑 USER root RUN $ORACLE_HOME/root.sh # 6. 运行期脚本 COPY --chown=oracle:oinstall entrypoint.sh /opt/oracle/entrypoint.sh RUN chmod +x /opt/oracle/entrypoint.sh VOLUME ["/opt/oracle/oradata"] EXPOSE 1521 5500 USER oracle ENTRYPOINT ["/opt/oracle/entrypoint.sh"]

oracle-database-preinstall-19c这个包非常值得用,它把创建用户组、设置内核参数、配置资源限制这些琐碎活儿全包了。如果没有可用的 yum 源,就得手工groupadd oinstall、groupadd dba、useradd -g oinstall -G dba oracle,再往/etc/sysctl.conf和/etc/security/limits.conf里写参数。手工写的时候要特别注意两点:kernel.shmmax要大于等于 SGA 大小,fs.aio-max-nr不要超过系统上限,否则安装程序的预检查会报错——虽然-ignorePrereqFailure能强制跳过,但跳过的后果会在建库阶段以更难看的方式出现。

3.3 静默安装的响应文件,几个字段决定成败

响应文件是手工构建的核心。它就是一个 key=value 的文本文件,告诉安装程序"别问我,按这个来"。下面这几个字段是必填且容易写错的:

oracle.install.option=INSTALL_DB_SWONLY UNIX_GROUP_NAME=oinstall INVENTORY_LOCATION=/opt/oracle/oraInventory ORACLE_HOME=/opt/oracle/product/19c/dbhome_1 ORACLE_BASE=/opt/oracle oracle.install.db.InstallEdition=EE oracle.install.db.OSDBA_GROUP=dba oracle.install.db.OSOPER_GROUP=dba oracle.install.db.OSBACKUPDBA_GROUP=dba oracle.install.db.OSDGDBA_GROUP=dba oracle.install.db.OSKMDBA_GROUP=dba oracle.install.db.OSRACDBA_GROUP=dba oracle.install.db.rootconfig.executeRootScript=false

INSTALL_DB_SWONLY的意思是"只装软件,不建库"。这是关键:建库放到容器启动时做,这样同一个镜像可以用不同的ORACLE_SID起多个库,也可以把数据目录挂出来持久化。

executeRootScript=false也很关键。在 Dockerfile 里安装程序是以oracle用户跑的,它没法切到 root,所以必须让它跳过自动执行root.sh,改成我们自己用USER root单独跑一遍。我第一次没设这个,安装跑到最后一步卡住报权限错误,查了半天。

再有一点:安装过程会产生大量日志,默认落在$ORACLE_BASE/oraInventory/logs和/tmp下。这些日志在构建阶段看着有用,进了镜像就是纯垃圾,可以在同一个RUN里顺手删掉。注意必须写在同一条 RUN 指令里,分成两条的话,前面那条产生的文件已经被固化成镜像层了,后面删也删不掉体积。

3.4 入口脚本:容器启动时到底要做什么

入口脚本是整个镜像的"大脑",它要做三件事:判断是否需要建库、需要的话就建、建完启动监听并保持前台运行。

逻辑大致是这样:

#!/bin/bash set -e # 如果数据目录里已经有控制文件,说明库已经建过了,直接启动 if [ -e "$ORACLE_BASE/oradata/$ORACLE_SID/system01.dbf" ]; then echo "Database already exists, starting up..." "$ORACLE_HOME/bin/sqlplus" -s / as sysdba <<EOF startup; exit; EOF else echo "No database found, creating..." /opt/oracle/createDB.sh fi # 跑一次性的 setup 脚本 for f in /opt/oracle/scripts/setup/*.sh; do [ -e "$f" ] || continue echo "Running setup script: $f" "$f" done mv /opt/oracle/scripts/setup /opt/oracle/scripts/setup.done # 跑每次启动都要跑的 startup 脚本 for f in /opt/oracle/scripts/startup/*.sh; do [ -e "$f" ] || continue "$f" done # 保持容器不退出 tail -f "$ORACLE_BASE/diag/rdbms/${ORACLE_SID,,}/$ORACLE_SID/trace/alert_$ORACLE_SID.log"

这里面有几个我从踩坑里学来的细节。

判断"库是否已存在"不要用"目录是否为空",要用"是否存在system01.dbf"。因为建库过程中会先创建目录和一堆临时文件,如果中途失败,目录非空但库不完整,用"目录非空"判断会导致下次启动直接跳过建库、然后启动失败,进入一个永远起不来的死循环。

setup脚本执行完要改名成setup.done。这是官方脚本的做法,很聪明:setup只应该跑一次(比如建业务用户、导入初始数据),而startup每次启动都要跑(比如开启某个会话级配置)。如果不改名,你每次重启容器都会重复执行建用户脚本,第二次就会报"用户已存在"。

最后保持容器运行,别用sleep infinity,用tail -f盯着 alert 日志。这样docker logs能看到数据库的真实运行日志,出问题时不用再docker exec进去翻文件,体验好很多。

4. 启动容器:参数里每一项都有原因

4.1 一条能直接抄的 run 命令

镜像有了,跑起来这一条:

docker run -d \ --name ora19c \ --shm-size=2g \ -p 1521:1521 \ -p 5500:5500 \ -e ORACLE_PWD='YourStrong#Passw0rd' \ -e ORACLE_CHARACTERSET=AL32UTF8 \ -e TZ=Asia/Shanghai \ -v /data/oracle/oradata:/opt/oracle/oradata \ -v /data/oracle/scripts/setup:/opt/oracle/scripts/setup \ --restart unless-stopped \ oracle/database:19.3.0-ee

逐个说为什么。

--shm-size=2g:这是最容易漏、后果最严重的一项。Docker 默认给容器的/dev/shm只有 64 MB,而 Oracle 的实例启动、建库、甚至某些查询都会用到共享内存段。不给这个参数,你会看到建库跑到一半报ORA-00845: MEMORY_TARGET not supported on this system,或者实例起来后一查大表就崩。2 GB 是个稳妥值,如果你打算给 SGA 配 4 GB,这个值也要跟着往上调,经验公式是shm-size ≥ SGA 大小。

ORACLE_PWD:不设置这个变量,容器会直接拒绝启动并打印提示。密码策略默认要求一定复杂度,我试过用纯数字密码,结果建库阶段失败,日志里能看到密码校验不通过。写密码时建议用单引号包起来,密码里带$、!、#这些字符时,双引号会被 shell 提前展开。

ORACLE_CHARACTERSET:只在首次建库时生效,建完之后改这个变量没有任何作用。这一点极其重要——字符集是建库时写死在数据字典里的,事后想改只能重建库或者做全库导出导入。所以第一次跑之前就想清楚,需要中文就AL32UTF8(现在基本都用这个),不要图省事用默认的WE8MSWIN1252。

TZ:设了之后容器的date才是本地时间。但这个变量有个隐藏前提:镜像里得有对应的时区数据文件。精简基础镜像常常没装tzdata,设了TZ也不生效,表现出来就是数据库日志时间比本地时间差 8 小时。稳妥做法是在 Dockerfile 里补一句安装tzdata,或者干脆在容器里手动ln -sf时区文件。

-v挂数据目录:这是所有数据库容器的生命线。不挂载的话,容器一删,你建的库、导的数据全没了,而且这个过程没有任何确认提示。挂载之后,docker rm只是删掉容器本身,数据还在宿主机上,重新 run 一个新容器挂同一个目录就能接着用。

4.2 首次启动的十几分钟,日志里在发生什么

docker run之后立刻docker logs -f ora19c,你会看到一段持续十几分钟的输出。理解这段输出,能让你在它"卡住"的时候知道该不该慌。

前一两分钟是初始化环境:创建密码文件、准备目录、检查内存和共享内存。这一段如果报错,基本都是shm-size或者内存不足的问题。

中间一大段是dbca建库:创建控制文件、创建 system/sysaux/undo 表空间、创建数据字典、编译内置存储过程。这一段最慢,在机械盘上跑二十分钟都正常。这时候docker logs可能会长时间没有新输出,别急着重启,去宿主机上docker stats看一眼,如果 CPU 和 I/O 还在动,说明它活着。

最后几分钟是配置监听、注册服务、跑setup脚本。看到类似"Database is ready to use"或者监听器ready的字样,就说明成了。

有个很实用的观察点:建库期间可以docker exec -it ora19c bash进去,tail -f /opt/oracle/cfgtoollogs/dbca/ORCLCDB/ORCLCDB.log,这个日志比容器 stdout 详细得多,能看到dbca具体卡在哪一步。我遇到过一次建库卡了四十分钟,进这个日志一看,是在创建 TEMP 表空间时报空间不足,宿主机磁盘满了。如果只看 stdout,你只会看到它一直在转。

4.3 连上去验证,别只看容器状态

容器Up不代表数据库能用。健康检查通过也不代表业务能连。我一般做三层验证。

第一层,容器内本地连:

docker exec -it ora19c bash -lc "sqlplus / as sysdba"

进去之后select status from v\$instance;,返回OPEN才算实例正常。如果返回MOUNTED或者报错,说明实例起来了但库没打开,通常是启动脚本里startup那句没执行成功。

第二层,容器内通过监听连:

docker exec -it ora19c bash -lc \ "sqlplus system/'YourStrong#Passw0rd'@//localhost:1521/ORCLPDB1"

这一步验证的是监听器和服务名注册。本地/ as sysdba走的是操作系统认证,不经过监听,所以它成功不代表监听没问题。很多"应用连不上但容器是好的"的问题,都卡在这一层。

第三层,宿主机通过端口映射连:

sqlplus system/'YourStrong#Passw0rd'@//127.0.0.1:1521/ORCLPDB1

这一步验证端口映射和宿主机防火墙。三层都过了,才算真的能用。

注意:19C 用的是容器数据库(CDB)架构,一个 CDB 下挂若干 PDB。应用连接时要连 PDB 的服务名(上面例子里的ORCLPDB1),而不是 CDB 的 SID。连错了会报ORA-12505,或者连上去了发现看不到自己的表——其实你连到 CDB 的根容器了,业务表在 PDB 里。

5. 常见问题与排查实录

5.1 构建阶段最常卡住的三个地方

构建跑到解压那一步报错。九成是安装介质的问题:下成了 Windows 版、下到一半断了导致 zip 不完整、或者文件名被改了导致脚本找不到。校验方法很简单,unzip -t LINUX.X64_193000_db_home.zip跑一遍,能正常列出文件清单才算完整。另外注意磁盘空间,解压 3 GB 的 zip 需要 5 GB 以上的临时空间,空间不够时unzip报的错误信息非常隐晦。

安装程序报预检查失败。常见的是kernel.shmmax太小、可用内存低于要求、fs.file-max不够。在 Docker 里,/etc/sysctl.conf里写的参数在构建阶段是不生效的——容器的内核参数由宿主机控制,镜像里写配置文件只是"给以后用"。所以预检查里的内核参数项,如果不打算在 run 的时候加--sysctl,直接用-ignorePrereqFailure跳过。但内存和磁盘这两项不能跳,跳了后面必然失败。

root.sh执行报错。这个脚本必须在 root 下、且ORACLE_HOME和oraInventory的权限正确时才不报错。常见错误是前面chown只做了$ORACLE_BASE没做$ORACLE_HOME,或者COPY进来的文件属主变成了 root。用COPY --chown=oracle:oinstall能避免大半问题。

5.2 启动阶段:报错信息与真实原因的对照

这一块我整理成一张表,都是实际遇到过的。

现象 / 报错大概率原因处理方式
容器启动几秒就退出,日志提示密码未设置没传ORACLE_PWD加上环境变量,注意特殊字符用单引号
ORA-00845: MEMORY_TARGET not supported/dev/shm太小加--shm-size=2g或更大
建库跑到一半失败,日志里有空间相关错误宿主机磁盘满df -h检查,清理或扩容后重跑
建库失败后重启,容器反复退出数据目录残留半成品,脚本判断逻辑有误清空挂载目录重新建,或改用system01.dbf判断
建库失败,提示内存不足宿主机内存不够,或 swap 未开加内存、加 swap,或降低建库内存参数
容器 Up,但应用连不上监听未起 / 服务名写错 / PDB 未打开按 4.3 的三层验证逐层排查
连上了但看不到表连到了 CDB 而不是 PDB服务名改成 PDB 的服务名
日志时间差 8 小时TZ未生效装tzdata或在容器内建时区软链
查询大表时容器被杀内存超限被 OOM Killer 干掉限制 SGA 大小,加--memory并留出余量
docker logs长时间无输出但没退出建库正在进行,正常现象看docker stats和 dbca 日志确认

关于"建库失败后反复退出"这一条,我再多说两句,因为这是最坑的。数据库建库是个不可逆的过程,跑到一半失败会在数据目录留下一堆半成品文件。如果入口脚本用"目录非空"来判断"库已存在",下次启动就会跳过建库、直接startup,然后因为数据文件不完整而失败,容器退出,重启策略又把它拉起来,形成无限循环。表现出来就是docker logs里反复刷同样的错误,一晚上能把日志刷到几个 GB。

正确的处理方式是:第一次建库失败,先docker rm -f掉容器,把挂载目录整个清空,查清楚失败原因(看 dbca 日志),再重新跑。不要指望它自己恢复。

5.3 使用阶段的几个坑

数据持久化了,但配置没持久化。只挂载了oradata目录的话,你后续改的listener.ora、tnsnames.ora、sqlnet.ora都在容器内,容器一删就没了。如果这些配置是业务必需的,要么把它们也挂出来,要么写成setup脚本在首次启动时自动生成。

容器重启后实例能起,但业务连不上。有一种情况是监听器的配置在容器重启后没跟着起来。检查lsnrctl status,如果监听没起,在startup脚本里加一句lsnrctl start。官方镜像的入口脚本处理了这一点,手工构建的容易漏。

备份忘了做。容器化之后很多人会误以为"有 Docker volume 就是安全的",其实 volume 只是存在宿主机上,宿主机磁盘挂了照样全没。数据库该做的逻辑备份、RMAN 备份一样都不能少,只是执行方式变成了docker exec或者起一个专职的备份容器。我先在一个测试环境里吃过亏:docker volume prune一条命令下去,把几个挂载的 volume 当成悬空卷清掉了,那次之后我在所有容器的 run 命令里都加了明确的-v路径,绝不用匿名卷。

5.4 独家小技巧:验证镜像是否真的可用

我有个习惯,每次做出一个新镜像,不急着用到项目里,先跑一个"最小可用性验证":起一个不挂载任何目录的临时容器,等它建完库,连进去建一张表、插一条数据、查出来,然后docker rm -f。这个过程能同时验证镜像构建正确、启动脚本逻辑正确、数据库功能正常三件事,耗时十几分钟,但能省掉后面在项目环境里排查的几小时。

还有个更省事的做法:把这个验证过程写成一个 shell 脚本,每次构建完自动跑一遍,失败就报警。这个脚本本身只有二十来行,但它在我们的流水线里挡掉过至少三次"看起来构建成功、实际起不来"的坏镜像。

6. 镜像瘦身与长期维护

6.1 体积从 7 GB 压到 5 GB,砍的是什么

镜像体积这件事,做之前要先想明白:省下来的空间到底是给谁省的。如果是本地开发用,7 GB 和 5 GB 区别不大,硬盘够;如果是 CI 流水线,每次 Job 都要拉一次镜像,那 2 GB 的差距可能意味着每次构建多等两分钟,一天几百次就是几小时的浪费,这时候瘦身就很有价值。

能砍的东西按收益排序:

第一是安装临时文件和解压出来的 zip。$ORACLE_HOME解压之后,/tmp下的 zip 就没用了,$ORACLE_BASE/oraInventory/logs里的安装日志也没用了。这两块能省 2~3 GB。关键是在同一个 RUN 指令里清理,否则清不掉。

第二是$ORACLE_HOME/install目录。这个目录里有安装时的响应文件、日志、临时脚本,装完之后运行期基本用不到。但要注意,官方镜像的入口脚本里可能会引用其中的文件,删之前先grep一遍启动脚本,确认没依赖。

第三是可选组件。$ORACLE_HOME下有一些开发验证用不到的组件目录,可以用chopt工具关掉。但这个操作有风险,关错了会导致某些功能报错,建议只关明确不用的。

我实测过一个版本:清理临时文件 + 删除 install 目录,镜像从 7.2 GB 降到 5.6 GB,启动和功能都正常。再往下压就要动组件了,收益变小、风险变大,不划算。

6.2 数据目录的规划比镜像更重要

很多人把注意力全放在镜像上,其实挂载目录的规划才是长期用得舒服的关键。我的习惯是按环境分开:

/data/oracle/dev/oradata # 开发环境数据 /data/oracle/dev/scripts # 开发环境初始化脚本 /data/oracle/test/oradata # 测试环境

每个环境一套独立的目录,容器名也带上环境前缀。这样同一台机器上可以同时跑好几个不同版本、不同ORACLE_SID的实例,互不干扰,出问题也好定位。目录命名一定要有规律,我见过用容器 ID 当目录名的,容器一删就再也对不上号了。

另外提醒一句,挂载目录的属主和权限要提前设好。宿主机上建的目录默认属主是 root,容器里的oracle用户(uid 通常是 54321)写不进去,表现出来就是建库时报"无法创建数据文件"。处理方式有两种:chown 54321:54321把目录给容器里的用户,或者 run 的时候加--user。前者简单,推荐。

6.3 版本升级时的重建策略

19C 的小版本升级,在容器里做和在物理机上做思路不一样。物理机上你会opatch apply打个补丁;容器里的常规做法是构建一个新镜像,然后用新镜像起新容器,挂载同一份数据目录。这要求两件事:新镜像的ORACLE_HOME路径和老的一致,数据字典版本兼容。

这个流程里有个必须注意的点:先用老镜像把数据库正常关闭,再用新镜像启动。直接docker kill然后用新镜像起,数据库处于异常关闭状态,起来之后要跑实例恢复,虽然一般能恢复成功,但如果你正好在做升级,这个不确定性没必要引入。

关闭的命令是:

docker exec -it ora19c bash -lc \ "sqlplus / as sysdba <<< 'shutdown immediate; exit;'"

确认实例已经关闭(docker logs里能看到 shutdown 完成的日志),再docker rm掉老容器,用新镜像 run 一个挂同一个数据目录的。这个顺序不能反。

至于大版本跨代升级(比如 11g 到 19C),容器化帮不上太多忙,该走的dbupgrade流程一样要走,只是可以在容器里做,做完把数据目录打包出来给新镜像用。这种情况下我一般不用"挂载原目录"的方式,而是导出导入或者用数据泵,干净利落,出了问题也好回退。

最后分享一个我在这个项目里形成的习惯:每做一个数据库镜像,就在仓库里配一份 README,写清楚这个镜像是怎么构建的、参数是什么、第一次启动要多久、挂载哪些目录、验证命令是哪几条。听起来很啰嗦,但半年后你要重建一个一模一样的镜像时,那几分钟的文档能省掉你半天时间。镜像本身是死的,构建过程才是资产。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询