Category: Linux
How to Turn PHP into an RPM The Easy Way with FPM
So you have a custom PHP binary that you wish to distribute across multiple servers.
The “old” way to do this is to create a bash script in order to pull down the source code, run ./configure and then make; make install. But this is 2013, we have a much more civilized way to create this package now, so that you can distribute that RPM across multiple nodes or even include it in your own yum repository.
We’re going to use FPM (Effing Package Manager) to do the job for this. It is a little bit confusing because PHP also includes the acronym FPM for FastCGI Process Manager. Our package manager FPM is a ruby gem, so install the prerequisites first (CentOS 6 directions here), along with fpm:
1 2 | # yum install ruby ruby-devel rubygems # gem install fpm |
To start, compile PHP the way you normally would, ie:
1 2 3 4 5 6 | './configure' '--program-prefix=' '--prefix=/usr' \ '--exec-prefix=/usr' '--bindir=/usr/bin' '--enable-ftp' \ '--enable-calendar' '--with-libxml-dir=/usr' '--enable-mbstring' \ '--enable-mbregex' '--with-gd' '--enable-zip' '--with-mcrypt' \ '--with-mhash' '--with-libdir=lib64' '--enable-fpm' '--with-fpm-user=www' \ '--with-fpm-group=www' '--with-mysql=mysqlnd' '--with-mysqli=mysqlnd' |
Then make it as normal.
1 2 | # make ... Lots of output |
Now instead of running make install like normal, we tell it to output to another directory, just some temporary directory on the server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | make install INSTALL_ROOT=/tmp/installdir Installing PHP SAPI module: fpm Installing PHP CLI binary: /tmp/installdir/usr/bin/ Installing PHP CLI man page: /tmp/installdir/usr/share/man/man1/ Installing PHP FPM binary: /tmp/installdir/usr/sbin/ Installing PHP FPM config: /tmp/installdir/etc/ Installing PHP FPM man page: /tmp/installdir/usr/share/man/man8/ Installing PHP FPM status page: /tmp/installdir/usr/share/fpm/ Installing build environment: /tmp/installdir/usr/lib/build/ Installing header files: /tmp/installdir/usr/include/php/ Installing helper programs: /tmp/installdir/usr/bin/ program: phpize program: php-config Installing man pages: /tmp/installdir/usr/share/man/man1/ page: phpize.1 page: php-config.1 /usr/local/src/php-5.3.25/build/shtool install -c ext/phar/phar.phar /tmp/installdir/usr/bin ln -s -f /usr/bin/phar.phar /tmp/installdir/usr/bin/phar Installing PDO headers: /tmp/installdir/usr/include/php/ext/pdo/ |
Now PHP is happily installed in /tmp/installdir. Which is great, but we can’t use it until it we package it up for regular installation. Run the FPM command to pack it all up:
1 2 3 4 5 | # fpm -s dir -t rpm -n php-fpm -v 5.3.25 -C /tmp/installdir \ -p php-fpm_VERSION_ARCH.rpm -d openssl -d pcre -d bzip2 -d curl \ -d libjpeg -d libpng -d freetype -d gmp -d libmcrypt -d libmhash \ -d libxml2 usr/bin usr/share/man/man1 usr/sbin etc \ usr/share/man/man8 usr/share/fpm usr/lib/build usr/include/php |
What do the options mean?
I’ll go through these options:
- -s dir: source is a directory
- -t rpm: we are creating an rpm
- -n php-fpm: we are creating a package with the name php-fpm
- -v 5.3.25: the version of the rpm (so you can manage upgrades)
- -C /tmp/installdir: the base location to take the files from
- -p php-fpm_VERSION_ARCH.rpm: fpm will replace the VERSION and ARCH macros automatically with your version number and architecture
- -d openssl -s pcre …: these are all of the rpm dependencies that you’ll want on the machines installing the package. If you don’t include the proper ones (to match your source system, and which are included in ./configure, you’ll get library errors when you try to launch the program.
- usr/bin usr/share/man/man1 usr/sbin etc usr/share/man/man8 usr/share/fpm usr/lib/build usr/include/php: the directories you wish to include in your package file
When all is done, you should have your rpm file located in /tmp/installdir. You can install it directly on any system with rpm -i <rpm name>
Before you package up your files, you can include any other files you want. For example, you could include the init script or php.ini file. But if you are managing the system using puppet or chef, it would be better to manage this configuration file through there rather than from the rpm file. This works really well as a ‘building block’ for your programs when you add these custom rpms into your own repository. That way, you can have your system install custom versions of the binaries where applicable but still have the niceties that come from an systems automation with puppet or chef.
Enjoy!
Simple Guide To Signing RPMs with FPM
I’ve been using the excellent fpm (Effing package manager!) tool for automatically generating rpms from source (for example, creating a custom compiled php-fpm binary and then wrapping it for install in an rpm for distribution from our own repository). FPM also creates .debs and other binaries as you need them.
To ensure we have a secure infrastructure we wanted to make sure that the rpms are correctly signed so that the gpg check can be completed on them when installed.
First, generate the gpg public/private key pair:
1 | gpg --gen-key |
Fill out the required information. The entropy generation step may take a long time to complete.
Export a text version of the public key with this command, for later use in your repository:
1 | gpg --export -a 'Dave Drager' > ~/RPM-GPG-KEY-reponame |
fpm docs state that you just need to add the –rpm-sign to your fpm command. However, this generated the following error for me:
1 2 | error: You must set "%_gpg_name" in your macro file {:level=>:error} Pass phrase check failed {:level=>:error} |
The reason for this is that it uses the settings located in your ~/.rpmmacros file. Find your key ID:
1 2 3 4 5 6 | # gpg --list-keys /user/.gnupg/pubring.gpg ------------------------ pub 2048R/94E8C1F6 2013-04-24 uid Dave Drager <ddrager@xxx> sub 2048R/94E8C1F6 2013-04-24 |
In this sample, the KEY_ID would be “94E8C1F6″
1 2 3 4 | %_signature gpg %_gpg_path /path/to/.gnupg %_gpg_name KEY_ID %_gpgbin /usr/bin/gpg |
From here, the fpm command runs without issue.
1 2 3 4 | # fpm -s dir -t rpm -n php-fpm-xda -v 5.2.23 -C /tmp/installdir -p php-fpm-xda_VERSION_ARCH.rpm -d openssl -d pcre -d bzip2 -d curl -d libjpeg -d libpng -d freetype -d gmp -d libmcrypt -d libmhash -d libxml2 --rpm-sign usr/bin usr/share/man/man1 usr/sbin etc usr/share/man/man8 usr/share/fpm usr/lib/build usr/include/php Enter pass phrase: Pass phrase is good. {:level=>:error} Created rpm {:path=>"php-fpm-xda_5.2.23_x86_64.rpm"} |
This is an alias of the rpm --addsign command.
Now let’s check the signature on this rpm.
1 2 | # rpm --checksig php-fpm-xda_5.2.23_x86_64.rpm php-fpm-xda_5.2.23_x86_64.rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#94E8C1F6) |
Whoops, it doesn’t seem to be signed properly. Well, that is because the rpm system doesn’t know to trust this GPG key yet. Import with the following command:
1 | rpm --import ~/RPM-GPG-KEY-reponame |
Note that this is the plain text file from before. Your RPM system knows to trust this key, and now if you verify the file:
1 2 | # rpm --checksig php-fpm-xda_5.2.23_x86_64.rpm php-fpm-xda_5.2.23_x86_64.rpm: rsa sha1 (md5) pgp md5 OK |
This RPM is now ready for use in a repo. In the .repo file, use the following to ensure it follows the correct signature:
1 2 | gpgcheck=1 gpgkey=http://repo.xxxx.com/RPM-GPG-KEY-reponame |
All key names have been changed to protect the innocent.
Using rsync on an Alternate SSH Port or with OpenSSH Keys
Best practices state that you should run ssh on an non-standard port. Unfortunately some programs use port 22 by default and it isn’t obvious what the switch is to change this port.
One of these programs is the eminently useful rsync. It states how to do this in the man file, but it is hidden and non-obvious. You simply add the option via the -e command to pass ssh options. -e is the shell rsync uses to connect to a remote host.
Using rsync on an alternate ssh port
1 | rsync -avz -e "ssh -p $port" username@ip:/path/to/files/ /local/files/ |
Using rsync with an openssh key
1 | rsync -avz -e "ssh -i /path/to/private/key" username@ip:/path/to/files/ /local/files/ |
Or with both an alternate port and openssh key:
1 | rsync -avz -e "ssh -i /path/to/private/key -p $port" username@ip:/path/to/files/ /local/files/ |
This can be used in a bash script if you set the $port variable or directly on the command line by using the set port instead of $port.
Here are some other useful pages regarding using rsync. If you don’t use rsync for remote file transfer – I highly recommend it. The transfer speed is much faster than scp over ssh.
Simple Sysadmin Trick: Using tcpdump To Sniff Web Server Traffic
Sometimes, you just have to look into the raw data to see what your web server is doing. The logs might not show you enough detail or you suspect something is going on which is just not shown in the log files. Or, as in my case, logging is turned off because of too much activity.
The excellent tcpdump utiliy comes to the rescue here. I recommend you get more familiar with the tcpdump man page. Here is the command you can use, in a nutshell:
1 | tcpdump -nl -w - -i eth1 -c 500 port 80|strings |
or alternatively with just tcpdump (Thanks Chris!):
1 | tcpdump -nl -s 0 -A -i eth1 -c 500 port 80 |
Your command line will print out all traffic exiting your server from port 80, headers and all. Lets look at the options in more detail.
- -n: Don’t convert addresses (i.e., host addresses, port numbers, etc.) to names.
- -l: Make stdout line buffered. Useful if you want to see the data while capturing it.
- -w: Write the raw packets to file rather than parsing and printing them out. (Sent to stdout)
- -i: Interface you want to sniff on, usually eth0 or eth1, but depends on your system.
- -c: Number of packets to capture
- port: port 80, duh :)
- -A: Print each packet (minus its link level header) in ASCII.
- -s: size
Now, depending on your web server configuration, you will probably have gzipped content which comes out as garbled characters. To strip all that out, just pipe it through strings.
The output will look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | GET /about/comment-page-1 HTTP/1.0 Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */* User-Agent: Mozilla/2.0 (compatible; MSIE 3.02; Windows CE; 240x320) Referer: http://quittingsoda.com/about/comment-page-1#comment-6637 Host: quittingsoda.com Cookie: comment_author_xxx=sakyjartory; comment_author_email_26e707905b5fd6e7139333eb1dab208f=olfaexxxx; comment_author_url_26e707905b5fd6e7139333eb1dab208xxx HTTP/1.1 200 OK Date: Wed, 03 Oct 2012 01:49:19 GMT Server: Apache/2 X-Powered-By: PHP/5.2.17 X-Pingback: http://quittingsoda.com/xmlrpc.php Vary: Accept-Encoding,User-Agent Connection: close Content-Type: text/html; charset=UTF-8 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head profile="http://gmpg.org/xfn/11"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>About This Site | Quitting Soda</title> |
Let me know if you have any suggestions for improving this simple command line program for sniffing your web server traffic! Netcat is an essential tool that every linux administrator should get to know better.
Evaluating FTP Servers: ProFTPd vs PureFTPd vs vsftpd
Usually, I will try to push clients towards using SCP (via a client such as WinSCP), however inevitably there are clients who do not understand this new method of accessing their files securely online, and who for one reason or another insist on using FTP for their online file access. As they say – the customer is always right?
Anyway, there are currently 3 mainstream FTP servers available via the yum command on CentOS 6.x. PureFTPd, ProFTPd and vsftpd. So which FTP server is the best? I will summarize the servers below, or skip to the summary.
ProFTPd
ProFTPd is a modular FTP server which has been around for a long time. The large control panels (cPanel, DirectAdmin) all support ProFTPd and have for years.
The most feature rich of the bunch is certainly ProFTPd. There are a ton of plugins available for it, and the creator of it modeled its configuration architecture much like Apache’s – it is also using the GPL for licensing.
Configuration of ProFTPd is fairly straight forward, and example configuration files abound at a quick search of Google.
ProFTPd is available on a wide variety of system architectures and operating systems.
ProFTPd Security
Of the bunch, ProFTPd has the most CVE vulnerabilities listed. The high number is most likely an indicator of ProFTPd’s widespread use which makes it a target of hackers.
ProFTPd CVE Entries: 40
Shodan ProFTPd entries: 127
PureFTPd
PureFTPd‘s mantra is ‘Security First.’ This is evident in the low number of CVE entries (see below).
Licensed under the BSD license, PureFTPd is also available on a wide-range of operating systems (but not Windows).
Configuration of PureFTPd is simple, with a no-configuration file option. Although not as widely used as ProFTPd, PureFTPd has many configuration examples listed online.
PureFTPd Security
PureFTPd’s “Security First” mantra puts it at the lead in the security department with the fewest security vulnerabilities.
PureFTPd CVE Entries: 4
Shodan Pure-FTPd Entries: 12
vsftpd
vsftpd is another GPL-licensed FTP server, which stands for “Very Security FTP daemon.” It is a lighweight FTP server built with security in mind.
Its lightweight nature allows it to scale very efficiently, and many large sites (ftp.redhat.com, ftp.debian.org, ftp.freebsd.org) currently utilize vsftpd as their FTP server of choice.
vsftpd Security
vsftpd has a lower number of vulnerabilities listed in CVE than ProFTPd but more than PureFTPd. This could be because, since its name implies it is a secure FTP service, or because it is so widely used on large sites – that it is under more scrutiny than the others.
vsftpd CVE Entries: 12
Shodan vsftpd entries: 41
Summary & FTP Server Recommendations
Considering the evaluations above, any server would work in a situation, however generally speaking:
- If you want a server with the most flexible configuration options and external modules: ProFTPd
- If you have just a few users and want a simple, secure FTP server: PureFTPd
- If you want to run a FTP server at scale with many users: vsftpd
Of course, everyone’s requirements are different so make sure you evaluate the options according to your own needs.
Disagree with my assessment? Let me know why!
Simple Disk Benchmarking in Linux Using ‘dd’
A great way to do a real-world disk test on your linux system is with a program called dd.
dd stands for data description and is used for copying data sources.
A simple command to do real-world disk write test in linux is:
1 | dd bs=1M count=512 if=/dev/zero of=test conv=fdatasync |
This creates a file named ‘test’ with all zeroes in it. The flag conv=fdatasync tells dd to sync the write to disk before it exits. Without this flag, dd will perform the write but some of it will remain in memory, not giving you an accurate picture of the true write performance of the disk.
A sample of the run is below, with a simple SATA disk:
1 2 3 4 | [14:11][root@server:~]$ dd bs=1M count=512 if=/dev/zero of=test conv=fdatasync 512+0 records in 512+0 records out 536870912 bytes (537 MB) copied, 5.19611 s, 103 MB/s |
Now, there is a major caveat for using dd for disk benchmarking. The first is that it only tests filesystem access. Depending on your filesystem (I’m looking at your ZFS) the file write may itself just load into memory for writing later down the road. The same with a RAID controller on the system.
A much more accurate way of performing a disk benchmark is to use tools specifically geared towards this task. It will write much more data over a longer period of time. Bonnie++ is a particularly useful tool for this purpose.
Now don’t forget to remove that test file.
What a Resilver Looks Like in ZFS (and a Bug and/or Feature)
At home I have an (admittedly small) ZFS array set up to experiment with this awesome newish RAID technology. I think it has been around long enough that it can now be used in production, but I’m still getting used to the little bugs/features, and here is one that I just found.
After figuring out that I had 2 out of 3 of my 1TB Seagate Barracuda hard drives in the array fail, I had to give the entire array up for a loss and test out my backup strategy. Fortunately it worked and there was no data loss. After receiving the replacement drives in from Seagate, I rebuilt the ZFS array (using raidz again) and went along my merry way. After another 6 months or so, I started getting some funky results from my other drive. Thinking it might have some issue as with the others, I removed the drive and ran Seatools on it (by the way, Seatools doesn’t offer a 64-bit Windows version – what year is this?).
The drive didn’t show any signs of failure, so I decided to wipe it and add it back into the array to see what happens. That, of course, is easier said than done.
One of the problems I ran into is that I am using Ubuntu and fuse to run zfs. Ubuntu has this nasty habit of changing around drive identifiers when USB devices are plugged in. So now when this drive is plugged in, it is on /dev/sde instead of /dev/sdd, which is now a USB attached drive.
No problem, I figure, I’ll offline the bad drive in the zpool and replace it with the new drive location. No such luck.
First I offlined the drive using zpool offline media /dev/sdd:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | dave@cerberus:~$ sudo zpool status pool: media state: DEGRADED status: One or more devices has been taken offline by the administrator. Sufficient replicas exist for the pool to continue functioning in a degraded state. action: Online the device using 'zpool online' or replace the device with 'zpool replace'. scrub: none requested config: NAME STATE READ WRITE CKSUM media DEGRADED 0 0 0 raidz1-0 DEGRADED 0 0 0 sdd OFFLINE 0 0 0 sdb ONLINE 0 0 0 sdc ONLINE 0 0 0 |
Now that it’s offline, I thought you should be able to detach it. No such luck – since it is a ‘primary’ device of the zpool it does not allow you to remove it.
1 2 | dave@cerberus:~$ sudo zpool detach media /dev/sdd cannot detach /dev/sdd: only applicable to mirror and replacing vdevs |
What they want you to do is replace the drive with another drive. This drive (the same drive, with all info wiped from it) is now on /dev/sde. I try to replace it:
1 2 3 4 5 6 7 8 | dave@cerberus:~$ sudo zpool replace media /dev/sdd /dev/sde invalid vdev specification use '-f' to override the following errors: /dev/sde is part of active pool 'media' dave@cerberus:~$ sudo zpool replace -f media /dev/sdd /dev/sde invalid vdev specification the following errors must be manually repaired: /dev/sde is part of active pool 'media' |
Even with -f it doesn’t allow the replacement, because the system thinks that the drive is part of another pool.
So basically you are stuck if trying to test a replacement with a drive that already been used in the pool. I’m sure I could replace it with another 1TB disk but what is the point of that?
I ended up resolving the problem by removing the external USB drive, therefore putting the drive back into the original /dev/sdd slot. Without issuing any commands, the system now sees the drive as the old one, and starts resilvering the drive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | root@cerberus:/home/dave# zpool status pool: media state: ONLINE status: One or more devices has experienced an unrecoverable error. An attempt was made to correct the error. Applications are unaffected. action: Determine if the device needs to be replaced, and clear the errors using 'zpool clear' or replace the device with 'zpool replace'. see: http://www.sun.com/msg/ZFS-8000-9P scrub: resilver in progress for 0h9m, 4.62% done, 3h18m to go config: NAME STATE READ WRITE CKSUM media ONLINE 0 0 0 raidz1-0 ONLINE 0 0 0 sdd ONLINE 0 0 13 30.2G resilvered sdb ONLINE 0 0 0 sdc ONLINE 0 0 0 |
It is interesting to see what it looks like from an i/o perspective. The system reads from the two good drives and writes to the new (bad) one. Using iostat -x:
1 2 3 4 5 6 7 8 | avg-cpu: %user %nice %system %iowait %steal %idle 29.77 0.00 13.81 32.81 0.00 23.60 Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util sda 0.00 0.00 0.80 0.00 33.60 0.00 42.00 0.01 15.00 15.00 1.20 sdb 0.00 0.00 625.00 0.00 108033.20 0.00 172.85 0.56 0.90 0.49 30.80 sdc 0.00 0.00 624.20 0.00 107828.40 0.00 172.75 0.50 0.81 0.47 29.60 sdd 0.00 1.20 0.00 504.40 0.00 107729.60 213.58 9.52 18.85 1.98 100.00 |
It seems that ZFS is able to identify a hard drive by GID somehow but doesn’t automatically use it in the pool. This makes it so that you can’t test a drive by removing it, formatting it, and putting it into a new location. Basically, zfs assumes that your drives are always going to be in the same /dev location, which isn’t always true. As soon as you attach a USB drive in Ubuntu things are going to shift around.
After the resilver is complete, the zpool status is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | root@cerberus:/home/dave# zpool status pool: media state: ONLINE status: One or more devices has experienced an unrecoverable error. An attempt was made to correct the error. Applications are unaffected. action: Determine if the device needs to be replaced, and clear the errors using 'zpool clear' or replace the device with 'zpool replace'. see: http://www.sun.com/msg/ZFS-8000-9P scrub: resilver completed after 0h16m with 0 errors on Sun May 15 07:35:46 2011 config: NAME STATE READ WRITE CKSUM media ONLINE 0 0 0 raidz1-0 ONLINE 0 0 0 sdd ONLINE 0 0 13 50.0G resilvered sdb ONLINE 0 0 0 sdc ONLINE 0 0 0 errors: No known data errors |
You can now clear the error with:
1 2 | root@cerberus:/home/dave# zpool clear media root@cerberus:/home/dave# |
Zpool status now shows no errors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | root@cerberus:/home/dave# zpool status pool: media state: ONLINE scrub: resilver completed after 0h16m with 0 errors on Sun May 15 07:35:46 2011 config: NAME STATE READ WRITE CKSUM media ONLINE 0 0 0 raidz1-0 ONLINE 0 0 0 sdd ONLINE 0 0 0 50.0G resilvered sdb ONLINE 0 0 0 sdc ONLINE 0 0 0 errors: No known data errors |
So now the question I have is this: Are you able to manually update or remove the drive status somewhere in your system? How did zfs know that this drive already had a pool installed on it? I zeroed the drive and verified with fdisk there were no partition on it. Is there a file somewhere on the system that stores this information, or is it written somewhere on the drive?
ZFS is great, but it still has some little issues like this that give me pause before using it in a production system. Then again, I suppose all massive disk array systems have their little quirks!

Recent Comments