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

时间:2018-05-17 17:07:34

标签: php ubuntu oci8 fpm instantclient

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

当我构建并安装OCI8扩展时,一切看起来都是有序的,但是尽管在PHP-FPM配置中启用了扩展,但它的存在并没有反映在phpinfo()的输出中。

以下Gist详细介绍了我用于配置,构建和安装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值(如指定的话)反映在Environment的{​​{1}}部分中(当通过PHP-FPM + NGINX呈现并从浏览器请求时) )和phpinfo()部分,PHP Variables

奇怪的是,即使将PHP-FPM的日志记录设置为$_SERVER['LD_LIBRARY_PATH'],我也看不到我在CLI中遇到的debug错误的任何痕迹。 OCI8扩展无法以静默方式加载。 { - 1}}在PHP-FPM的有效libmql1.so中也是如此。

我选择查看OCI8扩展是否在Apache中,在同一台服务器上运行,并且确实如此,只要我将display_startup_errors = On添加到php.ini;在缺席的情况下,Apache在创业时抱怨:

  

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

在Ubuntu 16.04 LTS上,并且根据我在此处的观察以及有关https://stackoverflow.com/a/45242468/1772379的评论,在Ubuntu 17.10和Ubuntu 18.04 LTS中发生了变化,这一切都与export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2无关。

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

我已在两个不同的Vagrant虚拟机/etc/apache2/envvars框6.0.0和LD_LIBRARY_PATH框v20180509.0.0上尝试了此操作,两者的行为相同。

任何其他想法都会非常感激!

编辑1

I asked about this issue on the package maintainer's GitHub tracker他建议问题源于在编译时未能设置适当的laravel/homestead

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

我确实注意到一个有趣的细节,即Ubuntu 18.04上的编译扩展使用ubuntu/bionic64(而不是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

根据评论者的建议,我在构建扩展程序之前重新检查了RUNPATH配置,并解决了问题!我之前尝试过这个,但是必须忽略构建尝试之间的某些事情:

RPATH

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

编辑4

我在之前的编辑中指出,修改# echo /opt/oracle/instantclient_12_2 > /etc/ld.so.conf.d/oracle-instantclient.conf # ldconfig 构成了一种更好的方法,但却意识到(根据评论者的好建议),这样做会导致不良的库冲突,因为效果是全系统。

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

我觉得我已经明确确定PHP-FPM守护程序忽略了Ubuntu上的ldconfig(并且至少已经有Ubuntu 16.04 LTS;请参阅注释以获得解释)。

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

  

使用环境变量LD_LIBRARY_PATH(除非可执行文件在安全执行模式下运行;请参阅下文)。 [sic]在这种情况下会被忽略。

到目前为止,我想不出任何其他可以忽略这条路径的原因。在LD_LIBRARY_PATH中,同一文件说:

ld.so(8)

首先,安全执行模式似乎没有生效,因为PHP可执行文件不会显示此标记(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. ):

AT_SECURE

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

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

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

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

AT_SECURE

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

# 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

3)我确保禁用AppArmor(它没有一个应该影响PHP-FPM的策略):

# 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

那么,为什么PHP-FPM忽略# getcap /usr/lib/php/20170718/oci8.so # getcap -r /opt/oracle/instantclient_12_2/ ,如果不是出于上述任何原因?

编辑5(解决方案)

精明的评论者@ vinc17指出,在运行# 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. 的系统上,环境变量(例如LD_LIBRARY_PATH)不一定会传播到通过systemd启动的进程单元。

换句话说,PHP-FPM不会忽略" LD_LIBRARY_PATH,而不是传达给这个过程。尝试在PHP-FPM配置中设置systemd是徒劳的,因为对值进行任何有用的事情都为时已晚。

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

毋庸置疑,我们希望避免编辑软件包维护者的文件(以避免在将来的升级过程中出现冲突),所以我们改为扩展它:

systemd

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

# 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

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

1 个答案:

答案 0 :(得分:1)

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

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

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

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

注意:LD_LIBRARY_PATH可能有助于查看哪些目录被视为搜索库。 编辑: straceldd是查找搜索路径发生情况的其他有用工具。

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

  

使用二进制文件的LD_LIBRARY_PATH动态部分属性中指定的目录(如果存在)。搜索此类目录只是为了找到DT_RUNPATH(直接依赖项)条目所需的那些对象,并且不适用于那些对象'孩子们,他们自己必须拥有自己的DT_NEEDED条目。这与DT_RUNPATH不同,后者适用于搜索依赖关系树中的所有子项。

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