php - 如何在带有 PHP-FPM 和 NGINX 的 Ubuntu 18.04 LTS 中使用 Oracle Instant Client 启用 OCI8 PHP 扩展?

标签 php ubuntu oci8 fpm instantclient

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

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

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

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

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

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

PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php/20160303/oci8.so' - libmql1.so: cannot open shared object file: No such file or directory in Unknown on line 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值,如指定,反射(reflect)在 Environment phpinfo()的部分(当通过 PHP-FPM + NGINX 呈现并从浏览器请求时)和 PHP Variables部分,如 $_SERVER['LD_LIBRARY_PATH'] .

奇怪的是,即使 PHP-FPM 的日志记录设置为 debug ,我没有看到 libmql1.so 的任何痕迹我在 CLI 中遇到的错误。 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 Warning: PHP Startup: Unable to load dynamic library 'oci8.so' (tried: /usr/lib/php/20170718/oci8.so (libmql1.so: cannot open shared object file: No such file or directory), /usr/lib/php/20170718/oci8.so.so (/usr/lib/php/20170718/oci8.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0



LD_LIBRARY_PATH无此业务在 Ubuntu 16.04 LTS 上是必需的,并且基于我在此的观察和关于 https://stackoverflow.com/a/45242468/1772379 的评论,这在 Ubuntu 17.10 和 Ubuntu 18.04 LTS 中发生了变化。

有没有其他人在 Ubuntu 18.04 LTS 上尝试过这个,特别是?

我已经在两个不同的 Vagrant VM 上尝试过这个,laravel/homestead框 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 (而不是 RPATH ,它在 Ubuntu 16.04 中使用)。如果 PHP-FPM 忽略 RUNPATH ,并且只查找 RPATH ,它将解释这种行为。

编辑 2 :

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

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

(通过对 use RPATH but not RUNPATH? 的评论发现)

编辑 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 守护进程会忽略 LD_LIBRARY_PATH在 Ubuntu 上(并且至少有 Ubuntu 16.04 LTS;请参阅注释以获取解释)。
ld.so(8)联机帮助页说明(与搜索运行时库路径的顺序有关):

Using the environment variable LD_LIBRARY_PATH (unless the executable is being run in secure-execution mode; see below). [sic] in which case it is ignored.



到目前为止,我想不出任何其他原因会忽略该路径。的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 ,而是它没有被传送到过程中。并试图设置 LD_LIBRARY_PATH在 PHP-FPM 配置中是徒劳的,因为对这个值做任何有用的事情都为时已晚。

根据这个建议,我想到设置 LD_LIBRARY_PATHsystemd上下文,即在启动 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 上的帖子。 .

最佳答案

一、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) 手册页的最新版本中有记录:

    Using the directories specified in the DT_RUNPATH dynamic section attribute of the binary if present. Such directories are searched only to find those objects required by DT_NEEDED (direct dependencies) entries and do not apply to those objects' children, which must themselves have their own DT_RUNPATH entries. This is unlike DT_RPATH, which is applied to searches for all children in the dependency tree.



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

    关于php - 如何在带有 PHP-FPM 和 NGINX 的 Ubuntu 18.04 LTS 中使用 Oracle Instant Client 启用 OCI8 PHP 扩展?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50396992/

    相关文章:

    php - 在Docker容器中使用php-fpm的ORA-28547

    php - PHP 中的 oci_connect 空白页

    php - 使用 glob 使用通配符查找完整文件名

    php - 限制字符串长度

    php - 指定非 JSON ajax 的内容类型

    php - Alpinelinux下编译PHP OCI8扩展

    php - MYSQL 查询可以直接工作,但不能通过 PHP!

    ruby-on-rails - 无法在 Ubuntu 下删除 Rails

    python - 将系统 python 安装的 sys.path 附加到我的个人 python 安装中

    python-3.x - Python 卡在计算中