Tuesday, November 20, 2018

How to Build Your Own Openshift Builder Image (PHP 5.6 with oci, pdo_oci, rfc)


This post is written to show how to build Openshift PHP builder image that are using Oracle database connections, specifically using PDO-OCI extension. OCI8 connection is also being prepared. 

Checkout 

The git repository I prepared in github have a branch for our specific purpose : 'pdo_oci8'

aws@broker ~ $ git clone https://github.com/yudhiwidyatama/s2i-php-container.git


aws@broker ~ $ cd s2i-php-container/

aws@broker s2i-php-container $ git checkout pdo_oci8
Branch pdo_oci8 set up to track remote branch pdo_oci8 from origin.
Switched to a new branch 'pdo_oci8'

Checking the 5.6/root directory, there is a file that shows the required Oracle instant client rpm files. 

aws@broker s2i-php-container $ cd 5.6
aws@broker 5.6 $ cd root

aws@broker root $ ls
opt  stage1.list.txt
aws@broker root $ cat stage1.list.txt 
-rw-r--r-- 1 root root 62587782 Oct 29 11:40 oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm
-rw-r--r-- 1 root root   634803 Oct 29 11:38 oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm
-rw-r--r-- 1 root root   852271 Oct 29 11:38 oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm

Then we need to download instant client from oracle. This is complicated by Oracle insisting us to sign-in using OTN Account. Open your browser and point to "https://www.oracle.com/technetwork/database/database-technologies/instant-client/downloads/index.html", click on "Instant Client for Linux x86-64".

Click Accept License Agreement, then proceed to click the required rpm files (basic, devel, and sqlplus for 12.1.0.2 version).  Do a dummy download using your browser (at this point Oracle would ask for your Oracle Account / OTN login, which we could request at no charge) and then copying the Link Address from browser, so we could use the Link Address URL in our wget commands.

The Url is obtainable from the Chrome's Downloads menu. Right click and choose 'Copy Link Address'.

Create another 'root' under the 'root' directory. Paste the URL between quotes to wget, and rename to take out AuthParam in the resulting file :

aws@broker root $ mkdir root
aws@broker root $ cd root
aws@broker root $ ls
aws@broker root $ pwd
/home/aws/s2i-php-container/5.6/root/root

aws@broker root $ wget "https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707896_f8e27ebee187829c54ba8692f79d720c"
--2018-11-20 16:56:51--  https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707896_f8e27ebee187829c54ba8692f79d720c
Resolving download.oracle.com... 104.93.96.131
Connecting to download.oracle.com|104.93.96.131|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 62587782 (60M) [application/x-redhat-package-manager]
Saving to: “oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707896_f8e27ebee187829c54ba8692f79d720c”

100%[===================================================================================================================================>] 62,587,782  33.7M/s   in 1.8s    

2018-11-20 16:56:53 (33.7 MB/s) - “oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707896_f8e27ebee187829c54ba8692f79d720c” saved [62587782/62587782]

aws@broker root $ mv oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm\?AuthParam\=1542707896_f8e27ebee187829c54ba8692f79d720c oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm
aws@broker root $ wget "https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707982_0f69007df1efaa246b884e175eb1e7b9"
--2018-11-20 16:58:23--  https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707982_0f69007df1efaa246b884e175eb1e7b9
Resolving download.oracle.com... 104.93.96.131
Connecting to download.oracle.com|104.93.96.131|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 634803 (620K) [application/x-redhat-package-manager]
Saving to: “oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707982_0f69007df1efaa246b884e175eb1e7b9”

100%[===================================================================================================================================>] 634,803     --.-K/s   in 0.04s   

2018-11-20 16:58:23 (14.8 MB/s) - “oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542707982_0f69007df1efaa246b884e175eb1e7b9” saved [634803/634803]

aws@broker root $ mv oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm\?AuthParam\=1542707982_0f69007df1efaa246b884e175eb1e7b9 oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm
aws@broker root $ wget "https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542708072_4faad0ac9a5562e854f08cadee1d87a8"
--2018-11-20 16:59:30--  https://download.oracle.com/otn/linux/instantclient/121020/oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542708072_4faad0ac9a5562e854f08cadee1d87a8
Resolving download.oracle.com... 104.93.96.131
Connecting to download.oracle.com|104.93.96.131|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 852271 (832K) [application/x-redhat-package-manager]
Saving to: “oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542708072_4faad0ac9a5562e854f08cadee1d87a8”

100%[===================================================================================================================================>] 852,271     --.-K/s   in 0.04s   

2018-11-20 16:59:30 (19.1 MB/s) - “oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm?AuthParam=1542708072_4faad0ac9a5562e854f08cadee1d87a8” saved [852271/852271]

aws@broker root $ mv oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm\?AuthParam\=1542708072_4faad0ac9a5562e854f08cadee1d87a8 oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm

Building

Check the rpm files :
aws@broker root $ ls
oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm  oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm  oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm

Proceed with building process :

aws@broker root $ cd ..
aws@broker root $ cd ..
aws@broker 5.6 $ pwd
/home/aws/s2i-php-container/5.6
aws@broker 5.6 $  docker build -t s2i-php56-pdooci .
Sending build context to Docker daemon 64.92 MB
Sending build context to Docker daemon 
Step 0 : FROM centos/s2i-base-centos7
 ---> ff41d164f277
Step 1 : EXPOSE 8080
 ---> Using cache
 ---> da1f2520288f
Step 2 : ENV PHP_VERSION 5.6 PATH $PATH:/opt/rh/rh-php56/root/usr/bin
 ---> Using cache
 ---> f944130c7708
Step 3 : ENV SUMMARY "Platform for building and running PHP $PHP_VERSION applications" DESCRIPTION "PHP $PHP_VERSION available as docker container is a base platform for building and running various PHP $PHP_VERSION applications and frameworks. PHP is an HTML-embedded scripting language. PHP attempts to make it easy for developers to write dynamically generated web pages. PHP also offers built-in database integration for several commercial and non-commercial database management systems, so writing a database-enabled webpage with PHP is fairly simple. The most common use of PHP coding is probably as a replacement for CGI scripts."
 ---> Using cache
 ---> 247b1e7eeb1b
...
Step 16 : RUN sed -i -f /opt/app-root/etc/httpdconf.sed /opt/rh/httpd24/root/etc/httpd/conf/httpd.conf &&     echo "IncludeOptional /opt/app-root/etc/conf.d/*.conf" >> /opt/rh/httpd24/root/etc/httpd/conf/httpd.conf &&     sed -i '/php_value session.save_path/d' /opt/rh/httpd24/root/etc/httpd/conf.d/rh-php56-php.conf &&     head -n151 /opt/rh/httpd24/root/etc/httpd/conf/httpd.conf | tail -n1 | grep "AllowOverride All" || exit &&     mkdir /tmp/sessions &&     chown -R 1001:0 /opt/app-root /tmp/sessions &&     chmod -R a+rwx /tmp/sessions &&     chmod -R ug+rwx /opt/app-root &&     chmod -R a+rwx /etc/opt/rh/rh-php56 &&     chmod -R a+rwx /opt/rh/httpd24/root/var/run/httpd
 ---> Running in e877a82cbebd
    AllowOverride All
 ---> 94bbfa64540a
Removing intermediate container e877a82cbebd
Step 17 : USER 1001
 ---> Running in b573ed446725
 ---> f11275cfcbe4
Removing intermediate container b573ed446725
Step 18 : CMD $STI_SCRIPTS_PATH/usage
 ---> Running in 84fd15f0d378
 ---> 27ab6f7675d2
Removing intermediate container 84fd15f0d378
Successfully built 27ab6f7675d2

Publishing to Docker Hub

Next step we tag and  publish the image:
aws@broker 5.6 $ docker tag s2i-php56-pdooci yudhiwidyatama/s2i-php56-pdooci:201801
aws@broker 5.6 $ docker push yudhiwidyatama/s2i-php56-pdooci:201801
The push refers to a repository [yudhiwidyatama/s2i-php56-pdooci] (len: 1)
27ab6f7675d2: Image already exists 
f11275cfcbe4: Image successfully pushed 
94bbfa64540a: Image successfully pushed 
47fb29191938: Image successfully pushed 
892ec8a5ac86: Image successfully pushed 
d633eb1d08f2: Image successfully pushed 
80831f5d7bb9: Image successfully pushed 
647b25811b6a: Image successfully pushed 
4049a4cf7897: Image successfully pushed 
eee73b56a072: Image successfully pushed 
beefd8a9f746: Image successfully pushed 
cdba5a3f763b: Image successfully pushed 
72906bd3915f: Image successfully pushed 
e071aabb7f3d: Image successfully pushed 
8a7c8fc193ee: Image already exists 
247b1e7eeb1b: Image already exists 
f944130c7708: Image already exists 
da1f2520288f: Image already exists 
ff41d164f277: Image already exists 
59c89aefca2f: Image successfully pushed 
c5013bcc9b43: Image already exists 
bb83746f161c: Image already exists 
c240b147d043: Image already exists 
581d3e9fafdb: Image successfully pushed 
3fa399969e3d: Image already exists 
3f12a77fa51e: Image already exists 
78d20169a434: Image already exists 
53a0573f2c8c: Image successfully pushed 
69852c4e3982: Image successfully pushed 
c8fa6e2ee625: Image already exists 
4d20e7d02f9a: Image already exists 
cc56be4d597b: Image already exists 
4b21f2a7e518: Image already exists 
9cc5960c49d3: Image already exists 
eb72e5beea78: Image already exists 
3993223e10aa: Image successfully pushed 
Digest: sha256:045bb0077acd4a910837955c2a676fb0985b84dca7db1f877c96dfd7813559eb

The image pushed successfully to Docker hub.

The hard part

Now I will try to describe the Dockerfile modifications (line 48-60).

First install the oracle instant client rpms and iproute :
# PDO_OCI, OCI8 extensions
RUN yum install -y iproute /root/oracle-instantclient12* && yum clean all -y && rm -rf /root/oracle-instantclient12*

Next copy the oci8 extension into modules directory :
COPY ./oci8.so /opt/rh/rh-php56/root/usr/lib64/php/modules/
The pdo_oci rpm also need to be copied and installed
COPY ./rh-php56-php-pdo_oci-5.6.25-1.el7.centos.x86_64.rpm /opt/app-root/

RUN rpm -ivh /opt/app-root/rh-php56-php-pdo_oci-5.6.25-1.el7.centos.x86_64.rpm
Oracle client library somehow not installed in the system, add the library by creating oracle12.conf file :
RUN echo "/usr/lib/oracle/12.1/client64/lib" > /etc/ld.so.conf.d/oracle12.conf 
RUN ldconfig 
Install the oci8 extension INI (the PDO_OCI already installed in the RPM, I think)

RUN echo "extension=oci8.so" > /opt/rh/rh-php56/root/etc/php.d/20-oci8.ini

The harder part is how to create the oci8.so and pdo_oci rpm, which would be described in  another post..


How To Build your Own Openshift Builder Images (saprfc extension for PHP 5.6)

This post shows the steps required to build your own Openshift Builder images. This might be needed when you need to connect to Oracle database using PDO extension, or there is additional command you want to be available in your application pods.
If you simply need additional extension that are readily available, there is an alternative simpler step involving the use of S2I assemble script (see http://inventorsparadox.blogspot.com/2018/04/installing-apcu-extension-in-openshift.html).
But if the extension need nontrivial dependencies, please read the remaining of this blog post.

Tools of the trade

We are going to need Docker toolset available. For Mac users, you could use Docker for Mac. For Windows users, there is Docker for Windows. I will show the screenshot from Docker running on CentOS Linux. You also going to need git (https://git-scm.com/download/win or  https://git-scm.com/download/mac). 

Clone the source repository

First clone the source code repository:
aws@broker ~ $ git clone https://github.com/yudhiwidyatama/s2i-php-container.git
Initialized empty Git repository in /home/aws/s2i-php-container/.git/
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 1201 (delta 0), reused 1 (delta 0), pack-reused 1197
Receiving objects: 100% (1201/1201), 121.52 MiB | 16.52 MiB/s, done.
Resolving deltas: 100% (519/519), done.
aws@broker ~ $ 
After that try to checkout the rfcfull branch :
aws@broker ~ $ cd s2i-php-container/
aws@broker s2i-php-container $ git branch -r
  origin/HEAD -> origin/master
  origin/apm
  origin/master
  origin/pdo_oci8
  origin/pdo_oci8_rfc
  origin/rfc
  origin/rfcfull
aws@broker s2i-php-container $ git checkout rfcfull
Branch rfcfull set up to track remote branch rfcfull from origin.
Switched to a new branch 'rfcfull'
aws@broker s2i-php-container $ git branch
  master
* rfcfull

Build

Lets try to build the image :
aws@broker s2i-php-container $ cd 5.6
aws@broker 5.6 $ docker build -t s2i-php56-rfc .
Sending build context to Docker daemon 
Post http:///var/run/docker.sock/v1.19/build?cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&memory=0&memswap=0&rm=1&t=s2i-php56-rfc: dial unix /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?
Seems that the user needs permission to access docker. Check the permission:
aws@broker 5.6 $ ls -l /var/run/docker.sock 
srw-rw----. 1 root root 0 Oct 23  2017 /var/run/docker.sock
It is owned by user root and group root. If you're using Centos, this should do the trick (run as root first):
broker ~ # usermod -aG root aws
broker ~ # 

Now retry (note that need to relogin because of the new group):
broker ~ # su - aws
aws@broker ~ $ id
uid=506(aws) gid=506(aws) groups=506(aws),0(root),490(dockerroot) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
aws@broker ~ $ cd s2i-php-container/
aws@broker s2i-php-container $ cd 5.6
aws@broker 5.6 $ docker build -t s2i-php56-rfc .
Sending build context to Docker daemon 846.8 kB
Sending build context to Docker daemon 
Step 0 : FROM centos/s2i-base-centos7
latest: Pulling from centos/s2i-base-centos7
3993223e10aa: Pull complete 
eb72e5beea78: Pull complete 
9cc5960c49d3: Pull complete 
4b21f2a7e518: Pull complete 
cc56be4d597b: Pull complete 
4d20e7d02f9a: Pull complete 
c8fa6e2ee625: Pull complete 
69852c4e3982: Pull complete 
53a0573f2c8c: Pull complete 
78d20169a434: Pull complete 
3f12a77fa51e: Pull complete 
3fa399969e3d: Pull complete 
581d3e9fafdb: Pull complete 
c240b147d043: Pull complete 
bb83746f161c: Pull complete 
c5013bcc9b43: Pull complete 
59c89aefca2f: Pull complete 
ff41d164f277: Pull complete 
Digest: sha256:a49c3ddec9fd5df400b7ba70075a9c65534bcf4aeab69ff1de18d3c7d16d655b
Status: Downloaded newer image for centos/s2i-base-centos7:latest
 ---> ff41d164f277
Step 1 : EXPOSE 8080
---> Using cache
---> da1f2520288f
Step 2 : ENV PHP_VERSION 5.6 PATH $PATH:/opt/rh/rh-php56/root/usr/bin
---> Using cache
---> f944130c7708
Step 3 : ENV SUMMARY "Platform for building and running PHP $PHP_VERSION applications" DESCRIPTION "PHP $PHP_VERSION available as docker container is a base platform for building and running various PHP $PHP_VERSION applications and frameworks. PHP is an HTML-embedded scripting language. PHP attempts to make it easy for developers to write dynamically generated web pages. PHP also offers built-in database integration for several commercial and non-commercial database management systems, so writing a database-enabled webpage with PHP is fairly simple. The most common use of PHP coding is probably as a replacement for CGI scripts."
---> Using cache

...
Step 17 : USER 1001
 ---> Running in 0dca667bb85f
 ---> 199df5b9f228
Removing intermediate container 0dca667bb85f
Step 18 : CMD $STI_SCRIPTS_PATH/usage
 ---> Running in 922e0b45384a
 ---> 666d38e20eeb
Removing intermediate container 922e0b45384a
Successfully built 666d38e20eeb



So the build was a success, now we only need to publish to Docker Hub. It actually isn't a mandatory to publish to docker hub, but the alternative is to publish to Openshift Internal Registry which is not quite  simple.

Publishing to Docker Hub

First examine that the image already built successfully :
aws@broker 5.6 $ docker images | grep s2i-php56-rfc
s2i-php56-rfc                                                                latest              666d38e20eeb        3 minutes ago       711.6 MB
Then retag the image (note: you should replace 'yudhiwidyatama' with you docker hub username):
aws@broker 5.6 $ docker tag s2i-php56-rfc yudhiwidyatama/s2i-php56-rfc:201801
Finally push the image, logging in along the way:
aws@broker 5.6 $ docker push yudhiwidyatama/s2i-php56-rfc:201801
The push refers to a repository [yudhiwidyatama/s2i-php56-rfc] (len: 1)
666d38e20eeb: Image push failed 

Please login prior to push:
Username: yudhiwidyatama
Password: 
Email: y**********@somewhere.com
WARNING: login credentials saved in /home/aws/.docker/config.json
Login Succeeded
The push refers to a repository [yudhiwidyatama/s2i-php56-rfc] (len: 1)
666d38e20eeb: Image already exists 
199df5b9f228: Image successfully pushed 
45071d907359: Image successfully pushed 
78405a7ecddd: Image successfully pushed 
1ba9df30e3cc: Image successfully pushed 
25d1fdac92c5: Image successfully pushed 
af57e9dca950: Image successfully pushed
c43b674aaedb: Image successfully pushed 
bb63b43c6289: Image successfully pushed 
8662170845ba: Image successfully pushed 
41771e55d58f: Image successfully pushed 
30713c6eb336: Image successfully pushed 
72906bd3915f: Image successfully pushed 
e071aabb7f3d: Image successfully pushed 
8a7c8fc193ee: Image successfully pushed 
247b1e7eeb1b: Image successfully pushed 
f944130c7708: Image successfully pushed 
da1f2520288f: Image successfully pushed 
...
59c89aefca2f: Image successfully pushed 
c5013bcc9b43: Image already exists 
bb83746f161c: Image already exists 
c240b147d043: Image already exists 
581d3e9fafdb: Image successfully pushed 
3fa399969e3d: Image already exists 
3f12a77fa51e: Image already exists 
78d20169a434: Image already exists 
53a0573f2c8c: Image successfully pushed 
69852c4e3982: Image successfully pushed 
c8fa6e2ee625: Image already exists 
4d20e7d02f9a: Image already exists 
cc56be4d597b: Image already exists 
4b21f2a7e518: Image already exists 
9cc5960c49d3: Image already exists 
eb72e5beea78: Image already exists 
3993223e10aa: Image successfully pushed 
Digest: sha256:b75d330d04b8b66a5231b9efd4082f684291eaff2906d956ab8f24f4b661b843

This process takes a while, even hours if your upload internet connection is slow. Now you could see the image successfully uploaded to Docker Hub :

Conclusion

In this blog I have shown how to build a rfc-enabled PHP 5.6 S2I  image using docker and upload the image to dockerhub.

The hard part

Actually I haven't discussed how to actually modify the docker image. The modification is in the lines 48-70 of the Dockerfile file.

The first three lines is to prepare directories that are we going to use in compiling the saprfc PHP extension :

RUN mkdir /opt/app-root/sap
RUN mkdir -p /usr/sap/rfcsdk
RUN mkdir /opt/app-root/saprfc-ext

The next two lines to add files from SAP's RFC SDK (extracted from file RFC_60-10003377.SAR from SAP Support) and PHP saprfc source code from http://saprfc.sourceforge.net/ :
ADD ./rfcsdk.tar.gz /usr/sap
ADD ./saprfc.tar.gz /opt/app-root

Note that the tar.gz file must be put in the same directory as the Dockerfile (check the github repo for resulting tar.gz).
Afterwards, install PHP 5.6 development package with the required compatibility library.

RUN yum install -y centos-release-scl && \
    INSTALL_PKGS="rh-php56-php-devel compat-libstdc++-33" && \
    yum install -y --setopt=tsflags=nodocs $INSTALL_PKGS && \
    rpm -V $INSTALL_PKGS && \
    yum clean all -y

The compilation process is done using phpize, configure, and make install.

RUN cd /opt/app-root/saprfc && \
 phpize && \
 ./configure && \
 make && \
 make install && \
 make clean

To enable the extension, an INI file is needed :

RUN echo "extension=saprfc.so" > /etc/opt/rh/rh-php56/php.d/99-saprfc.ini

And that is how you customize the Dockerfile.

Tuesday, April 10, 2018

Installing APCu Extension in Openshift Origin PHP Build Image

Background

Openshift Origin is an open-source software developed by Red Hat, that aims to make operating Kubernetes cluster simpler. In Openshift, a base build docker image is used and merged with application source codes to create deployment docker images. Sometimes a software that we need doesn't get included in base build image, which will hamper Openshift adoption.

Base rules

These are a few facts related to docker images, that enables the approach in this blog post :
A. Linux binary from the same operating system release could be copied and used in another Linux OS. In this case we need to create a dummy image with the same OS version of the docker images and copy the relevant PHP extension binary from the image.
B. Some directories are writable during the build process, some are not. When running a build, we do not have full privileges, we are running under a pre-configured default user. When application pods are running it will be running under another configured user. Both user are unable to write to certain directories. Check by executing 'cat > filename' and 'touch filename' commands in Pod console. 
C. Assemble script is going to be run during build time. We will add commands in this script in order to copy files from application source code to certain directories. Sometimes we need to experiment on the assemble script and checking the build logs for errors. Location of original assemble script might be different across platforms.

Initial Condition


For this blog article, we will be using standard Openshift Origin PHP 5.6 build image. Choose Add to Project, PHP, then PHP version 5.6.

Initially we don't have the APC extension in the resulting pods.

Target Condition

We are trying to run a PHP application using APCU extension, with phpinfo screen like this :


How-to

This is achieved by using these files in the application source code .sti/bin directory :

The key to enable extension installation is in the assemble script contents (which must be set to be executable, either locally by chmod u+x assemble or by git update-index ) :

#!/bin/bash
/usr/libexec/s2i/assemble
echo "Assembling.. "
CWD=`pwd`
echo "CWD = $CWD"
echo "short_open_tag=1" >> /etc/opt/rh/rh-php56/php.d/99-opentag.ini
cp .sti/bin/40-apcu.ini /etc/opt/rh/rh-php56/php.d/
ls -l /etc/opt/rh/rh-php56/php.d

The second line calls the original assemble script.
The sixth, create additional ini file for enabling PHP short open tag (which actually outside the context of this blog post). The seventh, copied apcu.ini file to the directory shown by phpinfo as "Scan this dir for additional .ini files".
The contents of apcu.ini actually altered to point to the extension file in the source directory:
; Enable APCu extension module
extension = /opt/app-root/src/.sti/bin/apcu.so

; This can be set to 0 to disable APCu
apc.enabled=1
... other content ..

Now, how do we obtain apcu.so ? My steps are a bit complicated, but for now thats all I have :
(note that you should install docker in your laptop first, create the .sti/bin folder, and change the some-directory to your internal path)
docker run -v /Some-directory/htdocs/application-name/.sti/bin:/tmp --user=root -it openshift/php-56-centos7 bash
Unable to find image 'openshift/php-56-centos7:latest' locally
latest: Pulling from openshift/php-56-centos7
a3ed95caeb02: Pull complete 
bcb99163e835: Pull complete 
6ed5d49af21b: Pull complete 
a2ecf21b138d: Pull complete 
Digest: sha256:abf2f0a2a8bc9919e345f9d311613849295ca5acfb1862042e8f7b49ee211532
Status: Downloaded newer image for openshift/php-56-centos7:latest
bash-4.2# whoami
root
bash-4.2#yum list | grep apc | grep php56 
more-php56-php-pecl-apcu.x86_64           4.0.10-1.el7.centos            remi-php56more-epel-7-x86_64
more-php56-php-pecl-apcu-debuginfo.x86_64 4.0.10-1.el7.centos            remi-php56more-epel-7-x86_64
more-php56-php-pecl-apcu-devel.x86_64     4.0.10-1.el7.centos            remi-php56more-epel-7-x86_64
bash-4.2# yum install more-php56-php-pecl-apcu
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.axarva.id
 * epel: epel.mirror.angkasa.id
 * extras: mirror.axarva.id
 * updates: mirror.axarva.id
Resolving Dependencies
--> Running transaction check
---> Package more-php56-php-pecl-apcu.x86_64 0:4.0.10-1.el7.centos will be installed
--> Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================
 Package                              Arch               Version                          Repository                                Size
=========================================================================================================================================
Installing:
 more-php56-php-pecl-apcu             x86_64             4.0.10-1.el7.centos              remi-php56more-epel-7-x86_64              64 k

Transaction Summary
=========================================================================================================================================
Install  1 Package

Total download size: 64 k
Installed size: 150 k
Is this ok [y/d/N]: Y
Downloading packages:
more-php56-php-pecl-apcu-4.0.10-1.el7.centos.x86_64.rpm                                                           |  64 kB  00:00:01     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : more-php56-php-pecl-apcu-4.0.10-1.el7.centos.x86_64                                                                   1/1 
  Verifying  : more-php56-php-pecl-apcu-4.0.10-1.el7.centos.x86_64                                                                   1/1 

Installed:
  more-php56-php-pecl-apcu.x86_64 0:4.0.10-1.el7.centos      

Complete!
bash-4.2# rpm -ql more-php56-php-pecl-apcu
/etc/opt/rh/rh-php56/php.d/40-apcu.ini
/opt/rh/rh-php56/root/usr/lib64/php/modules/apcu.so
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/INSTALL
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/LICENSE
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/NOTICE
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/README.md
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/TECHNOTES.txt
/opt/rh/rh-php56/root/usr/share/doc/pecl/apcu/TODO
/opt/rh/rh-php56/root/usr/share/licenses/more-php56-php-pecl-apcu-4.0.10
/opt/rh/rh-php56/root/usr/share/licenses/more-php56-php-pecl-apcu-4.0.10/LICENSE
/opt/rh/rh-php56/root/var/lib/pear/pkgxml/more-php56-php-pecl-apcu.xml

bash-4.2# cp /etc/opt/rh/rh-php56/php.d/40-apcu.ini /tmp/40-apcu.ini
bash-4.2# cp /opt/rh/rh-php56/root/usr/lib64/php/modules/apcu.so /tmp/apcu.so
bash-4.2# exit
exit


After copying via the /tmp volume, the files should exist in your .sti/bin folder.

Commit the .sti folder, push to repository, and then rebuild the application. And all should be good to go, but if not, check the build logs and deployed pod logs.

Conclusion

We could install additional extensions in Openshift Origin PHP image by putting the appropriate PHP extension binary in the application source code and creating a sti assemble script.



Sunday, December 10, 2017

Ruby Blues in Openshift Origin v2 (or How I broke my app by installing new relic gem)

The day I tried to install a Ruby gem for New Relic RPM, it causes almost a day  downtime for Phusion Passenger-run Rails Application. It turns out that there are several thing that worth noting about Ruby dependency management.

The First Error

The error message that causes downtime is like this :

Ruby (Rack) application could not be started

These are the possible causes:
  • There may be a syntax error in the application's code. Please check for such errors and fix them.
  • A required library may not installed. Please install all libraries that this application requires.
  • The application may not be properly configured. Please check whether all configuration files are written correctly, fix any incorrect configurations, and restart this application.
  • A service that the application relies on (such as the database server or the Ferret search engine server) may not have been started. Please start that service.
Further information about the error may have been written to the application's log file. Please check it in order to analyse the problem.
Error message:
Could not find newrelic_rpm-3.18.1.330 in any of the sources (Bundler::GemNotFound)
Exception class:
PhusionPassenger::UnknownError
Application root:
/var/www/openshift/console
The nature of this error is tightly coupled to how 'bundle' and 'gem install' works.

Bundle install and Gem.lock

If we execute bundle install, bundle would :
1) connect to the internet to check for latest gem versions
2) Sometimes update the gem.lock (need to explore further when it does) when finding newer gem version
3) install the gem using standard gem environment
These 3 might break your application during some circumstances. In my case, the environment running the application is a bit different than the environment in the command line.

Gem environment

The result of running 'gem environment' tells much about the situation :

RubyGems Environment:
  - RUBYGEMS VERSION: 1.8.24
  - RUBY VERSION: 1.9.3 (2013-11-22 patchlevel 484) [x86_64-linux]
  - INSTALLATION DIRECTORY: /opt/rh/ruby193/root/usr/local/share/gems
  - RUBY EXECUTABLE: /opt/rh/ruby193/root/usr/bin/ruby
  - EXECUTABLE DIRECTORY: /opt/rh/ruby193/root/usr/local/bin
  - RUBYGEMS PLATFORMS:
    - ruby
    - x86_64-linux
  - GEM PATHS:
     - /opt/rh/ruby193/root/usr/local/share/gems
     - /root/.gem/ruby/1.9.1
     - /opt/rh/ruby193/root/usr/share/gems
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :benchmark => false
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - http://rubygems.org/

Things to note here, the INSTALLATION DIRECTORY and GEM PATHS difference. The /usr/local/share/gems exist in GEM PATHS, as well as /usr/share/gems, but INSTALLATION will put the gems under /usr/local/share/gems.

The environment for running Ruby is a bit different too :


broker ~ # cat /var/www/openshift/console/script/console_ruby
export LD_LIBRARY_PATH=/opt/rh/ruby193/root/usr/local/lib64:/opt/rh/ruby193/root/usr/lib64:/opt/rh/v8314/root/usr/lib64
export GEM_HOME=/opt/rh/ruby193/root/usr/share/gems
export GEM_PATH=/opt/rh/root/usr/local/share/gems:/opt/rh/ruby193/root/usr/share/gems

ruby193-ruby $@

So the first GEM_PATH missed ruby193 prefix. Without the ruby193 prefix, the gems inside the actual directory would not be found, causing the error. Whats with the local directory anyway, this is not the first time I get confused by Linux/Unix directory scheme. It has historical significance but IMHO it is better not to have too many directories nowadays.

Solution to Problem I (or how to install newrelic_rpm in openshift broker&console)

Instead of fixing the GEM_PATH, I opt to use the install gem in the /usr/share/gems directory by using --install-dir :


gem install newrelic_rpm -v 3.18.1.330 --install-dir=/opt/rh/ruby193/root/usr/share/gems

Then add this one-liner in /var/www/openshift/broker/Gemfile

gem 'newrelic_rpm', '3.18.1.330'


Second error

Actually, before finding the solution above, another error crops up with these symptoms :


broker console # bundle install --local
Could not find rake-0.9.2.2 in any of the sources
broker console # bundle install
Fetching gem metadata from https://rubygems.org/...........
Fetching gem metadata from https://rubygems.org/..
Could not find openshift-origin-console-1.26.3.1 in any of the sources

As someone who are not familiar Ruby development, I found these two errors confusing. The first error, where bundle cannot find rake package, is very strange, because the 0.9.2.2 package is there :
broker console # gem list rake

*** LOCAL GEMS ***

rake (0.9.2.2)
broker console # ls -l /opt/rh/ruby193/root/usr/share/gems/gems | grep rake
drwxr-xr-x.  4 root root 4096 Nov 18  2014 rake-0.9.2.2
broker console #

Because version 0.9.2.2 is in available in the 'net (see https://rubygems.org/gems/rake/versions), I tried to use the internet by removing the --local keyword, and this time bundle complains about openshift-origin-console, which is not avaiable in the great internet.

The clue for the second error is the result of bundle config command :
broker console # bundle config
Settings are listed in order of priority. The top value will be used.

path
Set for your local app (/var/www/openshift/console/.bundle/config): "vendor"

disable_shared_gems
Set for your local app (/var/www/openshift/console/.bundle/config): "1"

Which is not the same with the neighboring application which has no settings at all:

broker broker # bundle config
Settings are listed in order of priority. The top value will be used.

broker broker # 

Solution to problem II

The solution is to remove .bundle/config file, which inadvertently created when I tried running bundle install --path vendor. The file redirects local gem searches into the ./vendor directory, thus skipping /opt/rh/ruby193/root/usr/share/gems directory and causing the 2nd error. The bundle program are unable to find the openshift gem location because it were searching the ./vendor directory.


broker console # ls -al
total 104
drwxr-x---. 14 apache apache  4096 Dec 10 20:46 .
drwxr-xr-x.  4 root   root    4096 Jun 15  2014 ..
drwxr-x---.  8 apache apache  4096 Dec  8 08:05 app
drwxr-xr-x.  2 root   root    4096 Dec 10 20:46 .bundle
drwxr-x---.  5 apache apache  4096 Dec  8 08:38 config
-rw-r-----.  1 apache apache   166 Jul 11  2014 config.ru
-rw-r-----.  1 apache apache   809 Dec  8 08:37 Gemfile
-rw-r--r--.  1 root   root    3487 Dec 10 20:31 Gemfile.lock
-rw-r--r--.  1 root   root    3453 Dec  8 07:18 Gemfile.lock.copy
drwxr-x---.  6 apache apache  4096 Dec  9 13:12 httpd
drwxr-x---.  2 apache apache  4096 Dec  8 08:38 log
drwxr-x---.  2 apache apache  4096 Jul 11  2014 .openshift
-rw-r-----.  1 apache apache 11754 Jul 11  2014 openshift-origin-console.spec
drwxr-x---.  3 apache apache  4096 Dec  8 08:05 public
-rw-r-----.  1 apache apache   398 Jul 11  2014 Rakefile
-rw-r-----.  1 apache apache  9208 Jul 11  2014 README.rdoc
drwxr-x---.  2 apache apache  4096 Jul 11  2014 run
drwxr-x---.  2 apache apache  4096 Dec  9 13:12 script
drwxr-x---.  7 apache apache  4096 Dec  8 08:05 test
drwxr-x---.  6 apache apache  4096 Dec  8 08:05 tmp
drwxr-x---.  6 apache apache  4096 Dec 10 20:46 vendor
-rw-r--r--.  1 root   root    1485 Dec 10 20:27 versionlist.log
broker console # rm -rf .bundle
broker console #

Now I can do bundle install --local again..

Lessons learned

What I learned, that is : bundle install are evil. It  install new gems and change application dependencies, sometimes this breaks your app (especially if your app is written by Red Hat and has missed some GEM_PATH). You must understood bundle's basic mechanism, because incorrect parameters will lead to breaking your application.