2016年5月2日 星期一

Nginx try_files 里的一个坑

今天下午 blog 某管理员踩到了 nginx try_files 的一个坑,导致 WordPress 博客采用了固定链接的页面无法访问。此问题持续半小时后修复。
原来的配置是这样的:
        location / {
            try_files $uri $uri/ /index.php;
            index  index.html index.htm index.php;
        }
        location ~ \.php$ { ... }
修改成了
        location / {
            try_files $uri $uri/ /index.php =404;
            index  index.html index.htm index.php;
        }
        location ~ \.php$ { ... }
增加的这个 =404,似乎是让 index.php 找不到的时候返回 404。但这么一改,形如https://servers.blog.ustc.edu.cn/2014/09/ustc-telecom-link-failure/ 这样的链接返回的竟然是 index.php 的源码!这是怎么回事呢?
这要从 nginx try_files 的工作原理说起。以 try_files $uri $uri/ /index.php; 为例,当用户请求 http://servers.blog.ustc.edu.cn/example 时,这里的 $uri 就是 /example。try_files 会到硬盘里尝试找这个文件。如果存在名为 /$root/example(其中 $root 是 WordPress 的安装目录)的文件,就直接把这个文件的内容发送给用户。显然,目录中没有叫 example 的文件。然后就看 $uri/,增加了一个 /,也就是看有没有名为/$root/example/ 的目录。又找不到,就会 fall back 到 try_files 的最后一个选项 /index.php,发起一个内部 “子请求”,也就是相当于 nginx 发起一个 HTTP 请求到http://servers.blog.ustc.edu.cn/index.php。这个请求会被 location ~ \.php$ { ... }catch 住,也就是进入 FastCGI 的处理程序。而具体的 URI 及参数是在 REQUEST_URI 中传递给 FastCGI 和 WordPress 程序的,因此不受 URI 变化的影响。
配置修改之后,/index.php 就不在 fall back 的位置了,也就是说 try_files 在文件系统里找到 index.php 之后,就会直接把文件内容(也就是 PHP 源码)返回给用户。这里的坑就是 try_files 的最后一个位置(fall back)是特殊的,它会发出一个内部 “子请求” 而非直接在文件系统里查找这个文件。

2016年4月22日 星期五

Install the latest Nginx and PHP in Ubuntu 16.04 with OpenCC, GeoIP and Imagick

#Install Aptitude
apt-get update
apt-get install aptitude

#Install Ubuntu Essential
aptitude update
aptitude dist-upgrade
aptitude install dstat build-essential fail2ban git
aptitude install python-software-properties

#Install the Latest Nginx:
add-apt-repository ppa:nginx/development
aptitude update
aptitude install nginx

#Install the Latest PHP7.0:
add-apt-repository ppa:ondrej/php
aptitude update
aptitude install php7.0-fpm php7.0-cli php7.0-curl php7.0-mysql php7.0-dev php7.0-cli php7.0-common php7.0-mbstring php7.0-intl

#Install the Latest GeoIP:
aptitude install libgeoip-dev
aptitude install git
git clone https://github.com/Zakay/geoip.git
cd geoip
phpize
./configure --with-php-config=/usr/bin/php-config
make
make install
echo "extension=geoip.so" > /etc/php/7.0/mods-available/geoip.ini
ln -s /etc/php/7.0/mods-available/geoip.ini /etc/php/7.0/cli/conf.d/20-geoip.ini
ln -s /etc/php/7.0/mods-available/geoip.ini /etc/php/7.0/fpm/conf.d/20-geoip.ini

#Install the Latest Imagick:
aptitude install libmagickwand-dev libmagickcore-dev
git clone https://github.com/mkoppanen/imagick.git
cd imagick
phpize
./configure --with-php-config=/usr/bin/php-config
make
make install
echo "extension=imagick.so" > /etc/php/7.0/mods-available/imagick.ini
ln -s /etc/php/7.0/mods-available/imagick.ini /etc/php/7.0/cli/conf.d/20-imagick.ini
ln -s /etc/php/7.0/mods-available/imagick.ini /etc/php/7.0/fpm/conf.d/20-imagick.ini

#Install the Latest OpenCC
aptitude install cmake doxygen
git clone https://github.com/BYVoid/OpenCC.git
cd OpenCC
make
sudo make install
git clone https://github.com/NauxLiu/opencc4php
cd opencc4php
phpize
./configure --with-php-config=/usr/bin/php-config
make
sudo make install
echo "extension=opencc.so" > /etc/php/7.0/mods-available/opencc.ini
ln -s /etc/php/7.0/mods-available/opencc.ini /etc/php/7.0/cli/conf.d/20-opencc.ini
ln -s /etc/php/7.0/mods-available/opencc.ini /etc/php/7.0/fpm/conf.d/20-opencc.ini


#Install Railgun
echo 'deb http://pkg.cloudflare.com/ wily main' | sudo tee /etc/apt/sources.list.d/cloudflare-main.list
curl -C - https://pkg.cloudflare.com/pubkey.gpg | sudo apt-key add -
aptitude update
aptitude install memcached railgun-stable

The Best my.cnf for Percona 5.7

[mysql]
socket = /var/run/mysql/mysql.sock

[mysqld]
user = mysql

bind-address = 0.0.0.0
port = 3306

basedir = /usr/local/mysql
pid-file = /var/run/mysql/mysql.pid
socket = /var/run/mysql/mysql.sock
datadir = /var/data/mysql
tmpdir = /tmp

# Log Path
#lc-messages-dir = /var/log/mysql
#lc_messages = en_US
log-error = /var/log/mysql/error.log


explicit_defaults_for_timestamp
skip-name-resolve

sql_mode = NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
symbolic-links = 0

max_allowed_packet              = 16M
max_connect_errors              = 1000000


thread_stack                    = 192K
thread_cache_size               = 50
max_connections                 = 500
#max_heap_table_size            = 2G
#tmp_table_size                 = 2G
table_open_cache                = 16384
table_definition_cache          = 16384
open_files_limit                = 65536
#key_buffer_size                = 512M
query_cache_type                = 0
#default_storage_engine         = TokuDB
#default_tmp_storage_engine     = TokuDB


# Innodb Settings
innodb_buffer_pool_size                         = 12G
innodb_buffer_pool_instances                    = 8
innodb_buffer_pool_dump_at_shutdown             = 1
innodb_buffer_pool_load_at_startup              = 1
innodb_flush_method                             = O_DIRECT
innodb_log_files_in_group                       = 2
innodb_log_file_size                            = 4G
innodb_flush_log_at_trx_commit                  = 0
innodb_change_buffering                         = all
# innodb_additional_mem_pool_size               = 32M
innodb_file_per_table                           = 1
innodb_read_io_threads                          = 16
innodb_write_io_threads                         = 16
innodb_io_capacity                              = 50000
#innodb_log_buffer_size                         = 16M
innodb_thread_concurrency                       = 0
#innodb_lock_wait_timeout                       = 120
innodb_open_files                               = 65536
innodb_autoinc_lock_mode                        = 1
#innodb_log_write_ahead_size                    = 4096
innodb_flush_neighbors                          = 0


#tokudb_loader_memory_size                      = 10G
#tokudb_directio                                = 1
#tokudb_cache_size                              = 25G
#tokudb_commit_sync                             = OFF
#tokudb_fsync_log_period                        = 1000
#tokudb_analyze_in_background                   = ON
#tokudb_analyze_mode                            = TOKUDB_ANALYZE_RECOUNT_ROWS


# BINARY LOGGING #
#log-bin = /var/data/mysql/mysql-bin
#sync-binlog                    = 1
#expire-logs-days               = 14

# Slow Query Log
#slow_query_log_file = /var/log/mysql/slow-query.log
slow_query_log                  = 0
long_query_time                 = 3

Build Percona 5.7 on Ubuntu 16.04

apt-get update
apt-get install aptitude

aptitude update
aptitude dist-upgrade
aptitude install dstat build-essential fail2ban git
aptitude install cmake libncurses5-dev libaio-dev libreadline6-dev zlib1g-dev
aptitude update

groupadd mysql
useradd -g mysql mysql
mkdir -p /etc/mysql
mkdir -p /var/data/mysql
mkdir -p /var/log/mysql
mkdir -p /var/run/mysql
chown -R mysql:mysql /usr/local/mysql
chown -R mysql:mysql /var/data/mysql
chown -R mysql:mysql /var/log/mysql
chown -R mysql:mysql /var/run/mysql

> Download Percona Server Latest Source
wget https://www.percona.com/downloads/Percona-Server-5.7/Percona-Server-5.7.11-4/source/tarball/percona-server-5.7.11-4.tar.gz
tar xvzf percona-server-5.7.11-4.tar.gz
cd percona-server-5.7.11-4/
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_CONFIG=mysql_release -DFEATURE_SET=community -DWITH_EMBEDDED_SERVER=OFF -DWITH_BOOST=~/boost -DDOWNLOAD_BOOST=1

make -j 8
make install

# If CMake got Error then remove CMakeCache.txt and run again.
rm CMakeCache.txt


cd /usr/local/mysql
cp ./support-files/mysql.server /etc/init.d/mysql
cp ./support-files/my-default.cnf /etc/mysql/my.cnf

# Add Path to system-wide enviroment variables applying to all users.
vim /etc/environment
Add "/usr/local/mysql/bin:" to the beginning of PATH
. /etc/environment

mysqld --initialize-insecure
#Regardless of platform, use --initialize for “secure by default” installation (that is, including generation of a random initial root password). In this case, the password is marked as expired and you will need to choose a new one. With the --initialize-insecure option, no root password is generated; it is assumed that you will assign a password to the account in timely fashion before putting the server into production use.

mysql_secure_installation
mysqld stop
# This command will ask you some inputs to set root password, remove anonymous users, disallow root login remotely, remove test database and reload privilege tables.

# Register Service
Create the service file in /etc/init.d/<service name>
chmod 700 /etc/init.d/<service name>

# Auto Start Up
update-rc.d mysql defaults
update-rc.d mysql enable

service --status-all

2016年3月12日 星期六

2016年2月28日 星期日

Raising the Maximum Number of File Descriptors (Open Files) on Ubuntu 14.04 Trusty

So, I think I reached a new milestone in my sysadmin career (I mainly identify as a developer, but am responsible for the infrastructure at Allmyles as well, currently.) The milestone I’m talking about is when you’re first hit with a downtime completely out of the blue, seeing some weird ‘Reached maximum number of open files’ error, or something similar.

Chances are, if you now need to research how to raise the limit, you didn’t even know before that such a limit existed, or like me, you had some vague idea, but never gave it much thought ('We don’t need to deal with this right now, surely we will revisit the issue before we even get close to the current limit.’) Well, no. You won’t. And when your services go down, it’s going to be hell to find any worthwhile resource that describes how to solve this thing, so you better do this now, or at the very least, set up some monitoring for the number of used file descriptors for critical processes!
If anyone’s interested, as I understand it, this entire feature is mostly obsolete — it seems like a nice safeguard against a certain type of memory leak (namely, accidentally opening too many files which each take up a bit of year memory), or for controlling resources available for each user, but it seems like it has practically no use nowadays, when file opens have practically no cost at all on most systems.
For reference, our current stack, for which the steps described below worked is:
  • Instances hosted on Amazon EC2 (not that this one should matter)
  • Ubuntu 14.04 (Trusty) for the OS
  • Supervisor for process management
  • And lastly, the process hitting errors was Elasticsearch

Realizing You Have A Problem is the First Step

If you’re currently trying to fix the issue on a live server, just skip on down to the next section. Otherwise, it’s probably a nice idea to check what your limit is set to, and how close you are to reaching it.
The canonical way to check the limits for your current session which everyone will tell you is the ulimit command:
$ ulimit -n
4096
$ ulimit -Hn
4096
$ ulimit -Sn
4096
A couple things to note:
  • There are separate limits for different users, so make sure to run this as the user your process is using.
  • There’s a hard limit, and a soft limit. The latter is the actual limit your processes have to obey, and the former set the maximum number the soft limit can be set to. If you need to set separate values for these two, you probably already know how to do that and are not reading this post, so just keep in mind to always modify both, and check the soft limit.
So this is what you would find after 10 seconds of Googling, but keep in mind that ulimit is not guaranteed to give you the limits your processes actually have! There’s a million things that can modify a limits of a process after (or before) you initialized your shell. So what you should do instead is fire up tophtopps, or whatever you want to use to get the ID of the problematic process, and do a cat /proc/{process_id}/limits:
$ cat /proc/1882/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             15922                15922                processes
Max open files            4096                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       15922                15922                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
Eek! Our Elasticsearch process has a max file limit of 4096, which is way less than we intended! Lucky for us /proc/{process_id}/fd is a directory that holds a file for each open file the process has, so it’s pretty easy to count how close we are to reach the limit:
$ sudo ls /proc/1882/fd | wc -l
4096
Welp, at least that explains why we’re seeing all those errors in the log. For the record, it took us 79 Elasticsearch indices to hit the 4096 open file limit. Oh well, let’s move on to actually fixing this.

The Stuff You Came Here to Read: Raising the Limit

Sorry it took this long to get here! The ulimit -n 64000 command that’s floating around, as every easy 'solution’, will not actually fix your problem. The issue is that the command only raises your limit for the active shell session, so it’s not permanent, and it most definitely will not affect your processes that are already running (actually, nothing will, so don’t have high expectations here.)
The actual way to raise your descriptors consists of editing three files:
  • /etc/security/limits.conf needs to have these lines in it:
    *    soft nofile 64000
    *    hard nofile 64000
    root soft nofile 64000
    root hard nofile 64000
    
    The asterisk at the beginning of the first two lines means 'apply this rule to all users except root’, and you can probably guess that the last two lines set the limit only for the root user. The number at the end is of course, the new limit you’re setting. 64000 is a pretty safe number to use
  • /etc/pam.d/common-session needs to have this line in it:
    session required pam_limits.so
    
  • /etc/pam.d/common-session-noninteractive also needs to have this line in it:
    session required pam_limits.so
    
I never got around to looking into what exactly this does, but I’d assume that these two files control whether the limits file you edited above is actually read at the beginning of your sessions.
So, you did it, great job! Just reboot the machine (yup, sadly, you need to) and your limits should reflect your changes:
$ ulimit -n
64000
$ ulimit -Hn
64000
$ ulimit -Sn
64000
Whee! And to check your problematic process again:
$ cat /proc/1860/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             15922                15922                processes
Max open files            4096                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       15922                15922                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
Wait, what? That still says 4096! There must be something we missed (except if you’re seeing 64000–in which case you can pat yourself on the back and close this tab.)

Finding That One Last Thing You Still Need to Do

So you stitched together and followed all the steps that you found on three different blogs, and four Stack Overflow questions, and it still won’t work. Huh?
The thing that most resources neglect to emphasize, is that your limits can really easily be modified by anything responsible for execution of your processes. If ulimit -n (run as the correct user) is giving you the number you just set, but cat /proc/{process_id}/limits is still printing the low number, you almost certainly have a process manager, an init script, or something similar messing your limits up. You also need to keep in mind that processes inherit the limits of the parent process. This last step (if necessary) is going to vary a lot based on your stack and configuration, so the best I can do is give you an example, of how our Supervisor setup had to be configured to fix the number of file descriptors.
Supervisor has a config variable that sets the file descriptor limit of its main process. Apparently, this setting is in turn inherited by any and all processes it launches. What’s even worse is that the default for this setting is 4096, and that’s no good when we’re wanting 64000 instead. To override this default, you can add the following line to/etc/supervisor/supervisord.conf, in the [supervisord] section:
minfds=64000
Pretty easy to fix, but also really hard to find if you have no knowledge of this inheritance rule, or the fact that Supervisor by default is not having any of that OS level limit configuration. At this point, all you have to do is restart supervisord, in my case withsudo service supervisor restart. This should automatically restart your problematic processes as well, with your newly set limit:
$ cat /proc/1954/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             15922                15922                processes
Max open files            64000                64000                files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       15922                15922                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
Neat! You got that thing fixed. Now go celebrate by setting this up on your other servers, too. And while you’re at it, you can tack on some monitoring as well.