【问题标题】:How does one enable the OCI8 PHP extension, using the Oracle Instant Client, in Ubuntu 18.04 LTS with PHP-FPM and NGINX?如何在带有 PHP-FPM 和 NGINX 的 Ubuntu 18.04 LTS 中使用 Oracle Instant Client 启用 OCI8 PHP 扩展?
【发布时间】:2018-10-28 01:11:07
【问题描述】:

我正在使用https://launchpad.net/~ondrej/+archive/ubuntu/php 提供的最新 PHP 包。

当我构建和安装 OCI8 扩展时,一切似乎都井然有序,但尽管在 PHP-FPM 配置中启用了扩展,但它的存在并未反映在 phpinfo() 的输出中。

以下要点详细说明了我用于配置、构建和安装 OCI8 PHP 扩展的确切过程:

https://gist.github.com/cbj4074/fa761f60b6f8db431539d76ebfba828e

相同的流程和配置在 Ubuntu 16.04 LTS 上运行良好,因此在 Ubuntu 18.04 LTS 上似乎存在一些根本差异,无论是操作系统还是相关的 PHP 包。

作为一些重要的(我怀疑与此问题相关)背景信息,在 Ubuntu 18.04 LTS 上,扩展无法在 CLI 环境中加载,并出现以下错误:

PHP 警告:PHP 启动:无法加载动态库 '/usr/lib/php/20160303/oci8.so' - libmql1.so:无法打开共享对象文件:在第 0 行的未知中没有此类文件或目录

我这样解决了这个问题:

# echo 'LD_LIBRARY_PATH="/opt/oracle/instantclient_12_2"' >> /etc/environment

我认为也许将LD_LIBRARY_PATH 添加到 PHP-FPM 环境配置中可能会解决那里的等效问题:

# echo "env['LD_LIBRARY_PATH'] = /opt/oracle/instantclient_12_2" >> /etc/php/7.2/fpm/pool.d/www.conf
# systemctl restart php7.2-fpm

这确实会导致LD_LIBRARY_PATH 值(如指定的那样)反映在phpinfo()Environment 部分(通过PHP-FPM + NGINX 呈现并从浏览器请求时)和PHP Variables部分,如$_SERVER['LD_LIBRARY_PATH']

奇怪的是,即使 PHP-FPM 的日志记录设置为 debug,我也看不到任何使用 CLI 时遇到的 libmql1.so 错误的痕迹。 OCI8 扩展只是无法静默加载。 display_startup_errors = On 在 PHP-FPM 的有效 php.ini 中也是如此。

我选择查看 OCI8 扩展是否在 Apache 中工作,在同一台服务器上,如果我将 export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 添加到 /etc/apache2/envvars,它确实可以;在其缺席的情况下,Apache 在启动时抱怨:

PHP 警告:PHP 启动:无法加载动态库 'oci8.so'(已尝试:/usr/lib/php/20170718/oci8.so(libmql1.so:无法打开共享对象文件:没有这样的文件或目录), /usr/lib/php/20170718/oci8.so.so (/usr/lib/php/20170718/oci8.so.so: 无法打开共享对象文件: 没有这样的文件或目录)) 在第 0 行的未知

在 Ubuntu 16.04 LTS 上,与 LD_LIBRARY_PATH 无关,根据我在此的观察和关于 https://stackoverflow.com/a/45242468/1772379 的 cmet,在 Ubuntu 17.10 和 Ubuntu 18.04 LTS 中发生了变化。

有其他人在 Ubuntu 18.04 LTS 上试过这个吗?

我已经在两个不同的 Vagrant VM(laravel/homestead box 6.0.0 和 ubuntu/bionic64 box v20180509.0.0)上进行了尝试,两者的行为都是相同的。

任何其他想法将不胜感激!

编辑 1

I asked about this issue on the package maintainer's GitHub tracker,他认为问题源于未能在编译时设置适当的RPATH

我在回复中解释说我正在设置了一个适当的值,但问题仍然关闭。

不过,我确实注意到了一个有趣的细节,那就是 Ubuntu 18.04 上的编译扩展使用 RUNPATH(而不是 Ubuntu 16.04 中使用的 RPATH)。如果 PHP-FPM 忽略 RUNPATH,而只查找 RPATH,它将解释这种行为。

编辑 2

这份仍然开放的报告看起来是介绍观察到的行为的绝佳候选者:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=859732

(通过 use RPATH but not RUNPATH?上的cmets发现)

编辑 3

根据评论者的建议,我在构建扩展程序之前重新检查了更新 ld 配置并解决了问题!我之前尝试过这个,但在构建尝试之间一定忽略了一些东西:

# echo /opt/oracle/instantclient_12_2 > /etc/ld.so.conf.d/oracle-instantclient.conf
# ldconfig

我仍然不知道为什么 LD_LIBRARY_PATH 在这种情况下不能正常工作,但是将 Instant Client 库路径添加到链接器配置似乎是一种更好的方法。

编辑 4

我在之前的编辑中表示,修改 ldconfig 是一种更好的方法,但我意识到(根据评论者的好建议)这样做会导致不良的库冲突,因为影响是系统范围的。

事后看来,通过LD_LIBRARY_PATH 将运行时库链接修改限制在执行环境中,最大限度地减少“附带损害”是有意义的。因此,我有动力确定为什么这在 Ubuntu 18.04 LTS 上不起作用。

我觉得我已经确定 PHP-FPM 守护程序在 Ubuntu 上忽略 LD_LIBRARY_PATH(并且至少从 Ubuntu 16.04 LTS 开始;请参阅评论以获取解释)。

ld.so(8) 联机帮助页状态(与搜索运行时库路径的顺序有关):

使用环境变量 LD_LIBRARY_PATH(除非可执行文件在安全执行模式下运行;见下文)。 [原文如此] 在这种情况下它会被忽略。

到目前为止,我想不出任何其他原因会忽略该路径。 secure-execution mode,同一份文件说:

 Secure-execution mode
       For  security reasons, the effects of some environment variables are voided or modified if the dynamic linker determines that the binary
       should be run in secure-execution mode.  (For details, see the discussion of individual environment variables below.)  A binary is  exe‐
       cuted  in  secure-execution  mode if the AT_SECURE entry in the auxiliary vector (see getauxval(3)) has a nonzero value.  This entry may
       have a nonzero value for various reasons, including:

       *  The process's real and effective user IDs differ, or the real and effective group IDs differ.  This typically occurs as a  result  of
          executing a set-user-ID or set-group-ID program.

       *  A process with a non-root user ID executed a binary that conferred capabilities to the process.

       *  A nonzero value may have been set by a Linux Security Module.

首先,安全执行模式似乎没有生效,因为 PHP 可执行文件没有显示此标志(AT_SECURE0):

LD_SHOW_AUXV=1 /usr/sbin/php-fpm7.1 -daemonize --fpm-config /etc/php/7.1/fpm/php-fpm.conf
AT_SYSINFO_EHDR: 0x7ffc569e1000
AT_HWCAP:        178bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x55ceab0c4040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f823c77f000
AT_FLAGS:        0x0
AT_ENTRY:        0x55ceab19e360
AT_UID:          0
AT_EUID:         0
AT_GID:          0
AT_EGID:         0
AT_SECURE:       0
AT_RANDOM:       0x7ffc56962349
AT_HWCAP2:       0x0
AT_EXECFN:       /usr/sbin/php-fpm7.1
AT_PLATFORM:     x86_64

在我看来,子 FPM 池进程可能会显示不同的 AT_SECURE 值,但 PHP-FPM 守护程序本身以及任何子进程的输出是相同的。父级和子级都具有以下值:

# od -t d8 /proc/851/auxv
0000000                   33      140722944548864
0000020                   16            395049983
0000040                    6                 4096
0000060                   17                  100
0000100                    3       93903778242624
0000120                    4                   56
0000140                    5                    9
0000160                    7      140365152313344
0000200                    8                    0
0000220                    9       93903779136352
0000240                   11                    0
0000260                   12                    0
0000300                   13                    0
0000320                   14                    0
0000340                   23                    0
0000360                   25      140722944193929
0000400                   26                    0
0000420                   31      140722944196579
0000440                   15      140722944193945
0000460                    0                    0

其次,鉴于以下情况,这些原因似乎都不适用:

1) 没有迹象表明 PHP-FPM 或其子进程具有不同的真实有效的用户或组 ID(感谢 https://unix.stackexchange.com/a/202359 的此命令):

# ps -e -o user= -o ruser= | awk '$1 != $2'
systemd+ systemd-timesync
systemd+ systemd-resolve
beansta+ beanstalkd
message+ messagebus
daemon   root
systemd+ systemd-network

# ps -e -o group= -o rgroup= | awk '$1 != $2'
systemd+ systemd-timesync
systemd+ systemd-resolve
beansta+ beanstalkd
message+ messagebus
daemon   root
systemd+ systemd-network

2) 有问题的二进制文件没有任何功能(以下命令不产生输出):

# getcap /usr/lib/php/20170718/oci8.so
# getcap -r /opt/oracle/instantclient_12_2/

3) 我已确保 AppArmor 已禁用(无论如何,它没有应该影响 PHP-FPM 的策略):

# systemctl disable apparmor
Synchronizing state of apparmor.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable apparmor
# reboot
# aa-status
apparmor module is loaded.
0 profiles are loaded.
0 profiles are in enforce mode.
0 profiles are in complain mode.
0 processes have profiles defined.
0 processes are in enforce mode.
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

那么,如果不是出于上述任何原因,为什么 PHP-FPM 会忽略 LD_LIBRARY_PATH

编辑 5(解决方案)

一位精明的评论者@vinc17 指出,在运行systemd 的系统上,环境变量(例如LD_LIBRARY_PATH)不一定会传播到通过systemd 单元启动的进程。

换句话说,PHP-FPM 并没有“忽略”LD_LIBRARY_PATH,而是没有将其传递给进程。并且尝试在 PHP-FPM 配置中设置 LD_LIBRARY_PATH 是徒劳的,因为对值做任何有用的事情已经太晚了。

根据这个建议,我想到在 systemd 上下文中设置 LD_LIBRARY_PATH,即在启动 PHP-FPM 守护程序的单元文件中,在这种情况下 PHP-FPM 加载OCI8 扩展成功。

不用说,我们希望避免编辑包维护者的文件(以避免在未来升级期间发生冲突),因此我们对其进行了扩展:

# mkdir /etc/systemd/system/php7.1-fpm.service.d
# touch /etc/systemd/system/php7.1-fpm.service.d/environment.conf

我们在这个文件中添加以下内容:

[Service]
Environment=LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2

并使更改生效:

# systemctl daemon-reload
# systemctl restart php7.1-fpm

有关解决多个共同安装的 PHP 版本的更完整示例,请参阅我在 https://github.com/oerdnj/deb.sury.org/issues/865#issuecomment-395441936 的帖子。

【问题讨论】:

  • 非常好的分析系统,虽然我不完全理解你的解决方案(Edit5)。那现在只有php-fpm的解决方案吗?您的 php cli 是否适用于 oci8?我遇到了 apache 和 php cli 的问题,所以我使用 Edit3 的解决方案。
  • @Gunni 谢谢!编辑 5 中描述的解决方案仅在从 systemd 中启动 PHP-FPM 守护程序时才相关。此外,Edit 5 不是 only 解决方案;编辑 3 中的那个会“工作”,但它有缺点(请参阅对答案的评论)。对于 PHP CLI,只需 echo 'LD_LIBRARY_PATH="/opt/oracle/instantclient_12_2"' >> /etc/environment。至于 Apache 的 mod_php,虽然我还没有测试过,但我怀疑如果它是从 systemd 内部启动的,那么 Edit 3 或 Edit 5 解决方案将是必要的(我怀疑stackoverflow.com/a/45242468/1772379 的“旧”方法会起作用)。
  • 对我来说,“旧”解决方案确实适用于 Ubuntu 18.04 中的 modapache。无论如何,我选择第三种方法,因为它是我的解决方案,它具有最少的文件数来编辑以“让它工作”(tm)无处不在(apache,cli)。我阅读了可能的冲突,但对我来说这些不适用。

标签: php ubuntu oci8 fpm instantclient


【解决方案1】:

首先,Debian bug 859732 是一个完全不同的问题(我什至会说一个相反的问题):对于这个错误,搜索路径中存在几个版本的库(一个在 LD_LIBRARY_PATH 指定的某个目录中,一个在运行路径指定的某个目录中存在不同的目录),但动态链接器选择了错误的目录。

在您的情况下,问题是在搜索路径中的任何地方都找不到请求的库。另请注意,在您的情况下,似乎是 PHP 尝试打开库(通过 dlopen?),因为消息以“PHP 警告:”开头。但是,机制似乎与通常的动态链接相同。

安装库后,您需要的至少是以下一项:

  • 如果库已安装在默认搜索的目录中,则没什么特别的。由于您遇到错误,这不是您的情况。
  • 在运行路径中提供目录,该目录必须在需要该库的软件的编译时指定。问题在于,在 Linux 下,构建工具并没有按照标准完成此操作,并且在不破坏其他内容的情况下正确完成此操作可能很复杂。但是,在dlopen 的上下文中,该软件(这里是 PHP)可能已经设置了一个可以称为“插件搜索路径”的东西,您可以在其中放置您的库。
  • 提供LD_LIBRARY_PATH 中的目录。这是您尝试过的,但您的 LD_LIBRARY_PATH 似乎不正确。库通常安装在名为lib(或lib32lib64 在特定情况下)的子目录中。所以,export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 似乎是错误的。搜索库的完整路径名oci8.so,并将此路径名的目录部分取为LD_LIBRARY_PATH

注意:strace 可能有助于查看哪些目录被视为搜索库。 编辑: lddobjdump -p 是其他有用的工具,可用于查找搜索路径的情况。

编辑 2: 选择使用运行路径时要注意的另一点是,在使用 RPATH 时会发现间接库依赖关系,但在使用 RUNPATH 时不会发现(因此,在在后一种情况下,所有依赖项如果它们依赖于其他库,则需要有一个运行路径,以便可以在不诉诸LD_LIBRARY_PATH 的情况下找到所有库。这在 ld.so(8) 手册页的最新版本中有记录:

使用在二进制文件的DT_RUNPATH 动态部分属性中指定的目录(如果存在)。仅搜索此类目录以查找 DT_NEEDED(直接依赖项)条目所需的那些对象,并且不适用于这些对象的子对象,这些对象本身必须具有自己的 DT_RUNPATH 条目。这与DT_RPATH 不同,DT_RPATH 应用于搜索依赖关系树中的所有子项。

这可能就是为什么在不使用 LD_LIBRARY_PATH 的情况下,这适用于 16.04(使用 RPATH)但不适用于 18.04(使用 RUNPATH)。

【讨论】:

  • @BenJohnson strace 给出了系统调用的痕迹,这样你就永远不会在那里看到提到字符串LD_LIBRARY_PATH,但你可能会看到它提供的目录,例如如您所见,在/opt/oracle/instantclient_12_2/libclntsh.so.12.1 中。 LD_LIBRARY_PATH 未被考虑到其他库的事实可能是 PHP 中的一个错误(它也可能是一个安全功能,在这种情况下,发现 libclntsh.so.12.1 的事实可能存在漏洞)。我建议你联系 PHP 开发人员。
  • @BenJohnson 嗯...经过一番思考,找到了oci8.so(可能来自 PHP 中的某个运行路径),但问题是它的必要条件之一(libmql1.so)不能加载。不过,我不明白为什么 LD_LIBRARY_PATH 被考虑到 libclntsh.so.12.1 而不是 libmql1.so。可能是不同的机制。库上的ldd(设置和不设置LD_LIBRARY_PATH)和objdump -p 可能会提供更多有趣的信息。
  • 这样的问题是Instant Client install doc 试图让您使用 ldconfig 而不是 LD_LIBRARY PATH 的原因。
  • @BenJohnson ldd 可以找到libmql1.so(在/usr/lib,虽然不在预期的位置)但通过 PHP 却找不到。无论如何,如您所见,/usr/lib/php/20170718/oci8.so 有一个RUNPATH/opt/oracle/instantclient_12_2,但没有/opt/oracle/instantclient_12_2/libclntsh.so.12.1,我认为这应该是保持一致性的方法。另请注意,使用ldconfig/opt/oracle/instantclient_12_2 添加到默认目录可能不是一个好主意,因为这对机器来说是全局的,并且可能与其他软件发生冲突。
  • @BenJohnson 我找到了解释。请参阅我的第二次编辑。基本上,这是由于将RPATH 更改为RUNPATH,在间接库依赖方面表现不同。请注意,这仍然与Debian bug 859732 无关,这是由于RPATHRUNPATH 之间的另一个区别。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-23
  • 1970-01-01
  • 2014-02-10
  • 2016-08-30
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
相关资源
最近更新 更多