highlight.js

2017年8月5日土曜日

Scrapy + ProxyMesh を使って SSL (https) サイトを分散スクレイピング

Scrapy を ProxyMesh 経由で使った際にハマったので、メモを残したいと思います。

環境

  • Python 3.3
  • Scrapy 1.3.3

Scrapy とは?

Scrapy は Python のスクレイピングフレームワークです。
ググるとたくさん情報が出てきますので、詳しくはそちらにお任せします。(手抜きすぎw)

ProxyMesh とは?

ProxyMesh はHTTP の Proxy で IP を分散してくれます。
こちらもググるとそこそこ情報が出てきますので、詳しくはそちらにお任せします。(丸投げすぎw)

何がハマった?

それで、何にハマったかというと、SSL (https) サイトのスクレイピングです。
ProxyMesh のドキュメントでは、Scrapy の場合、以下のように書かれています。
For the scrapy crawling framework, you must set http_proxy environment variable, as shown above, then activate the HttpProxyMiddleware.
なので、環境変数にProxyMeshを指定して、非SSL (http) サイトでテストすると、IP が ProxyMesh で分散されることを確認できました。
次に、SSL (https) サイトでテストすると、 ProxyMesh の IP ではなく、自分の IP でアクセスしていました。
半日ほど調べましたが、結局わからず途方に暮れていましたが、もしかしてと思い、https_proxy 環境変数に変えてみると、うまく動きました。
原因は、未だに不明ですが、きっと urllib とかが関係している気がしています。(全然違うかもしれませんが)

まとめ

ということで、Scrapy + ProxyMesh で SSL (https) サイトをスクレイピングするとき、環境変数は、 http_proxy ではなく、https_proxy を使いましょう。私の環境だけだっだりして、、、

2017年7月31日月曜日

Zabbix の vfs.dev.read / write って、Linux の場合 Agent で何してるの?

ストレージサーバーのディスクを read only でマウントする事件があり、Zabbix でディスクへの書き込みをチェックがしたくなった。
そこで、既存の vfs.dev.read / write が 使えるのか知りたくなったので調べて見た。

環境

  • CentOS release 6.8 (Final)
  • zabbix-agent-2.2.15-1.el6.x86_64

ソースコードをダウンロード

まずは、ここ(https://www.zabbix.com/download)から調べたいソースコードをダウンロード。

適当に grep しまくる

それっぽいところにたどり着く

#if defined(KERNEL_2_4)
# define INFO_FILE_NAME "/proc/partitions"
# define PARSE(line) if (sscanf(line, ZBX_FS_UI64 ZBX_FS_UI64 " %*d %s "   \
     ZBX_FS_UI64 " %*d " ZBX_FS_UI64 " %*d "   \
     ZBX_FS_UI64 " %*d " ZBX_FS_UI64 " %*d %*d %*d %*d", \
    &rdev_major,       \
    &rdev_minor,       \
    name,        \
    &ds[ZBX_DSTAT_R_OPER],      \
    &ds[ZBX_DSTAT_R_SECT],      \
    &ds[ZBX_DSTAT_W_OPER],      \
    &ds[ZBX_DSTAT_W_SECT]      \
    ) != 7) continue
#else
# define INFO_FILE_NAME "/proc/diskstats"
# define PARSE(line) if (sscanf(line, ZBX_FS_UI64 ZBX_FS_UI64 " %s "   \
     ZBX_FS_UI64 " %*d " ZBX_FS_UI64 " %*d "   \
     ZBX_FS_UI64 " %*d " ZBX_FS_UI64 " %*d %*d %*d %*d", \
    &rdev_major,       \
    &rdev_minor,       \
    name,        \
    &ds[ZBX_DSTAT_R_OPER],      \
    &ds[ZBX_DSTAT_R_SECT],      \
    &ds[ZBX_DSTAT_W_OPER],      \
    &ds[ZBX_DSTAT_W_SECT]      \
    ) != 7        \
    &&        \
    /* some disk partitions */     \
    sscanf(line, ZBX_FS_UI64 ZBX_FS_UI64 " %s "   \
     ZBX_FS_UI64 ZBX_FS_UI64     \
     ZBX_FS_UI64 ZBX_FS_UI64,    \
    &rdev_major,       \
    &rdev_minor,       \
    name,        \
    &ds[ZBX_DSTAT_R_OPER],      \
    &ds[ZBX_DSTAT_R_SECT],      \
    &ds[ZBX_DSTAT_W_OPER],      \
    &ds[ZBX_DSTAT_W_SECT]      \
    ) != 7        \
    ) continue
#endif

結論

Zabbix の vfs.dev.read / write は Linux kernel 2.6 の場合、/proc/diskstats をパースしている。
それで、/proc/diskstats というのは、「I/O statistics」つまり統計情報なので、今回監視したかったのと
は少し違った。
やっぱり、Agent で touch /tmp/write_checkする用のアイテムを作るか、リモートコマンドでサーバーから touch して戻り値をチェックする感じかな。

参考

2017年7月21日金曜日

Downloading pip packages

PyPIで欲しかったバージョンが無くなっていて、困ったことがあるので、パッケージをダウンロードして、ローカルでインストールできるようにする。

ダウンロードするパッケージ

# cat pip.txt
Django==1.8.15
django-debug-toolbar==1.6
mysqlclient==1.3.9
Pillow==3.4.2
python-dateutil==2.5.3
requests==2.11.1
django-formtools==1.0
google-api-python-client==1.5.4
pycrypto==2.6.1
python-memcached==1.58
django-pagination==1.0.7

ディレクトリを作る

# mkdir pip_packages

ダウンロード

# pip install -r pip.txt --download pip_packages/
DEPRECATION: pip install --download has been deprecated and will be removed in the future. Pip now has a download command that should be used instead.
Collecting Django==1.8.15 (from -r pip.txt (line 1))
  Using cached Django-1.8.15-py2.py3-none-any.whl
  Saved ./pip_packages/Django-1.8.15-py2.py3-none-any.whl
Collecting django-debug-toolbar==1.6 (from -r pip.txt (line 2))
  Using cached django_debug_toolbar-1.6-py2.py3-none-any.whl
  Saved ./pip_packages/django_debug_toolbar-1.6-py2.py3-none-any.whl
Collecting mysqlclient==1.3.9 (from -r pip.txt (line 3))
  Using cached mysqlclient-1.3.9.tar.gz
  Saved ./pip_packages/mysqlclient-1.3.9.tar.gz
Collecting Pillow==3.4.2 (from -r pip.txt (line 4))
  Downloading Pillow-3.4.2-cp27-cp27mu-manylinux1_x86_64.whl (5.6MB)
    100% |????????????????????????????????| 5.6MB 197kB/s
  Saved ./pip_packages/Pillow-3.4.2-cp27-cp27mu-manylinux1_x86_64.whl
Collecting python-dateutil==2.5.3 (from -r pip.txt (line 5))
  Using cached python_dateutil-2.5.3-py2.py3-none-any.whl
  Saved ./pip_packages/python_dateutil-2.5.3-py2.py3-none-any.whl
Collecting requests==2.11.1 (from -r pip.txt (line 6))
  Using cached requests-2.11.1-py2.py3-none-any.whl
  Saved ./pip_packages/requests-2.11.1-py2.py3-none-any.whl
Collecting django-formtools==1.0 (from -r pip.txt (line 7))
  Using cached django_formtools-1.0-py2.py3-none-any.whl
  Saved ./pip_packages/django_formtools-1.0-py2.py3-none-any.whl
Collecting google-api-python-client==1.5.4 (from -r pip.txt (line 8))
  Using cached google_api_python_client-1.5.4-py2.py3-none-any.whl
  Saved ./pip_packages/google_api_python_client-1.5.4-py2.py3-none-any.whl
Collecting pycrypto==2.6.1 (from -r pip.txt (line 9))
  Using cached pycrypto-2.6.1.tar.gz
  Saved ./pip_packages/pycrypto-2.6.1.tar.gz
Collecting python-memcached==1.58 (from -r pip.txt (line 10))
  Saved ./pip_packages/python_memcached-1.58-py2.py3-none-any.whl
Collecting django-pagination==1.0.7 (from -r pip.txt (line 11))
  Saved ./pip_packages/django_pagination-1.0.7-cp27-none-any.whl
Collecting sqlparse>=0.2.0 (from django-debug-toolbar==1.6->-r pip.txt (line 2))
  Downloading sqlparse-0.2.2-py2.py3-none-any.whl
  Saved ./pip_packages/sqlparse-0.2.2-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil==2.5.3->-r pip.txt (line 5))
  Using cached six-1.10.0-py2.py3-none-any.whl
  Saved ./pip_packages/six-1.10.0-py2.py3-none-any.whl
Collecting httplib2<1dev>=0.8 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
  Saved ./pip_packages/httplib2-0.9.2-cp27-none-any.whl
Collecting oauth2client<5 .0.0dev="">=1.5.0 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
  Using cached oauth2client-4.0.0-py2.py3-none-any.whl
  Saved ./pip_packages/oauth2client-4.0.0-py2.py3-none-any.whl
Collecting uritemplate<4dev>=3.0.0 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
  Using cached uritemplate-3.0.0-py2.py3-none-any.whl
  Saved ./pip_packages/uritemplate-3.0.0-py2.py3-none-any.whl
Collecting rsa>=3.1.4 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
  Using cached rsa-3.4.2-py2.py3-none-any.whl
  Saved ./pip_packages/rsa-3.4.2-py2.py3-none-any.whl
Collecting pyasn1>=0.1.7 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
  Using cached pyasn1-0.1.9-py2.py3-none-any.whl
  Saved ./pip_packages/pyasn1-0.1.9-py2.py3-none-any.whl
Collecting pyasn1-modules>=0.0.5 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
  Using cached pyasn1_modules-0.0.8-py2.py3-none-any.whl
  Saved ./pip_packages/pyasn1_modules-0.0.8-py2.py3-none-any.whl
Successfully downloaded Django django-debug-toolbar mysqlclient Pillow python-dateutil requests django-formtools google-api-python-client pycrypto python-memcached django-pagination sqlparse six httplib2 oauth2client uritemplate rsa pyasn1 pyasn1-modules

ダウンロードしたパッケージをインストール


# pip install --no-index --find-link=pip_packages -r pip.txt
Ignoring indexes: https://pypi.python.org/simple
Collecting Django==1.8.15 (from -r pip.txt (line 1))
Collecting django-debug-toolbar==1.6 (from -r pip.txt (line 2))
Collecting mysqlclient==1.3.9 (from -r pip.txt (line 3))
Collecting Pillow==3.4.2 (from -r pip.txt (line 4))
Collecting python-dateutil==2.5.3 (from -r pip.txt (line 5))
Collecting requests==2.11.1 (from -r pip.txt (line 6))
Collecting django-formtools==1.0 (from -r pip.txt (line 7))
Collecting google-api-python-client==1.5.4 (from -r pip.txt (line 8))
Collecting pycrypto==2.6.1 (from -r pip.txt (line 9))
Collecting python-memcached==1.58 (from -r pip.txt (line 10))
Collecting django-pagination==1.0.7 (from -r pip.txt (line 11))
Collecting sqlparse>=0.2.0 (from django-debug-toolbar==1.6->-r pip.txt (line 2))
Collecting six>=1.5 (from python-dateutil==2.5.3->-r pip.txt (line 5))
Collecting httplib2<1dev>=0.8 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
Collecting oauth2client<5 .0.0dev="">=1.5.0 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
Collecting uritemplate<4dev>=3.0.0 (from google-api-python-client==1.5.4->-r pip.txt (line 8))
Collecting rsa>=3.1.4 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
Collecting pyasn1>=0.1.7 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
Collecting pyasn1-modules>=0.0.5 (from oauth2client<5 .0.0dev="">=1.5.0->google-api-python-client==1.5.4->-r pip.txt (line 8))
Building wheels for collected packages: mysqlclient, pycrypto
  Running setup.py bdist_wheel for mysqlclient ... done
  Stored in directory: /root/.cache/pip/wheels/b1/fe/93/1c6edc2bed53b006150e95039bbf298733fc3d26407c1040b5
  Running setup.py bdist_wheel for pycrypto ... done
  Stored in directory: /root/.cache/pip/wheels/a5/97/74/570ba69687b0b8df3fd6ddf02ed44f5b82681296f83fc22353
Successfully built mysqlclient pycrypto
Installing collected packages: Django, sqlparse, django-debug-toolbar, mysqlclient, Pillow, six, python-dateutil, requests, django-formtools, httplib2, pyasn1, rsa, pyasn1-modules, oauth2client, uritemplate, google-api-python-client, pycrypto, python-memcached, django-pagination
Successfully installed Django-1.8.15 Pillow-3.4.2 django-debug-toolbar-1.6 django-formtools-1.0 django-pagination-1.0.7 google-api-python-client-1.5.4 httplib2-0.9.2 mysqlclient-1.3.9 oauth2client-4.0.0 pyasn1-0.1.9 pyasn1-modules-0.0.8 pycrypto-2.6.1 python-dateutil-2.5.3 python-memcached-1.58 requests-2.11.1 rsa-3.4.2 six-1.10.0 sqlparse-0.2.2 uritemplate-3.0.0

インストールできました。これをコミットするなりすれば、PyPIからパッケージが無くなっても、安心ですね!

追記

ダウンロードの時に、以下のエラーが出てました。

DEPRECATION: pip install --download has been deprecated and will be removed in the future. Pip now has a download command that should be used instead.
「--download」オプションはDeprecationなので、「-d」オプションを使いましょう。


2017年7月14日金曜日

Apacheメモリチューニング

Apache2.2 をデフォルトのまま使用すると、メモリを使い切ってしまう可能性があるため、チューニングが必要になる。

環境

  • CentOS 6.8
  • httpd-2.2.15-54.el6.centos.x86_64

Apache2.2 デフォルト

  • MPM prefork
  • MaxClients 256

メモリ1GBリバースプロキシの場合


まず、現在の1プロセスあたりのメモリ使用量を調べる。
$ ps aux | grep 'httpd' | egrep -v 'grep|root' | awk 'BEGIN{sum=0;} {sum+=$6} END{print sum/NR;}'
4480.3
このサーバーでMaxClients分の256コネクション使い切った場合、
4.3MB x 256 = 1100.8MB
となり、メモリの割り当てが1GBの場合、スワップまたはOOMが発生してしまうと予想される。
また、ssh できるメモリも無く、制御不能になるだろう。

チューニング

MPMをworkerに変更するだけでとりあえずOK。他はデフォルトのまま。

  • MPM worker
  • ServerLimit 16
  • MaxClients 300
  • ThreadsPerChild 25

MPMをworkerに変更したところ、1プロセスあたりのメモリが13MBになった。
MaxClientsが16で、ThreadsPerChildが25なので、最大プロセス数は12。
13MB x 12 = 156MB
メモリが1GBあれば全然問題なし。そればかり、preworkより最大コネクション数が44多い。
チューニング次第では、最大で1425コネクションくらいイケる。

メモリ2GB mod_wsgiの場合

$ ps aux | egrep 'USER|httpd' | egrep -v 'grep|root'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
apache    2560  0.0  0.3 188192  7628 ?        S    06:36   0:01 /usr/sbin/httpd
apache    7832  0.0  0.3 188192  7624 ?        S    07:10   0:01 /usr/sbin/httpd
apache    7833  0.0  0.3 188192  7624 ?        S    07:10   0:01 /usr/sbin/httpd
apache    7834  0.0  0.3 188192  7624 ?        S    07:10   0:01 /usr/sbin/httpd
apache    8145  0.0  0.3 188192  7624 ?        S    07:13   0:01 /usr/sbin/httpd
apache    8438  0.0  0.3 188192  7628 ?        S    07:14   0:01 /usr/sbin/httpd
apache   12965  0.0  0.3 188192  7624 ?        S    07:45   0:01 /usr/sbin/httpd
apache   15250  2.9  3.8 1538260 74348 ?       Sl   May28  51:44 /usr/sbin/httpd
apache   15251  2.9  4.4 1543012 84260 ?       Sl   May28  51:45 /usr/sbin/httpd
apache   15412  0.0  0.3 188192  7628 ?        S    08:00   0:00 /usr/sbin/httpd
apache   19639  0.0  0.3 188192  7624 ?        S    08:28   0:00 /usr/sbin/httpd
apache   19797  0.0  0.3 188192  7552 ?        S    08:29   0:00 /usr/sbin/httpd

これは、Apache + prefork + mod_wsgi + Djangoで、mod_wsgiをDaemon mode(processes 2, threads 20)で動かしている。RSSが大きい2つがmod_wsgi。
この場合でもやはり、MaxClients分の256コネクションで2GBをApacheが使い切ってしまう可能性がある。

チューニング

なので、以下のように設定。

  • MPM worker
  • ServerLimit 16
  • MaxClients 300
  • ThreadPerChild 25
  • mod_wsgi Daemon mode(processes 9, threads 25)

Apacheの設定は、MPMをworkerに変えただけで、他はデフォルトのまま。
staticのファイルは、Apacheがそのままレスポンスを返すので、mod_wsgiはDaemon modeのまま。access_logからmod_wsgiとstaticのアクセス比率が、3:1 だったので、MaxClients 300を1として、mod_wsgiが3になるようにしつつ、Apacheが2GBの8割以下になるように調整。

今後

Apacheの使っていないモジュールを外せば、最大コネクション数をもっと増やせそう。
しかし、Nginxをリバースプロキシとして動かす場合、1GBメモリで最大で25,000コネクション捌けるらしい。
今のApacheがコネクションを捌ききれなくなったら、スケールする前に、Nginxへの切り替えを検討した方がコスト的にいいかもしれない。

2017年7月13日木曜日

Plesk メールブラックリストに登録されてしまった時の対処法

レンタルサーバーを運用していると、
  1. メールアドレスが乗っ取られ
  2. スパムの踏み台
  3. IPアドレスがブラックリストに登録
と言うことがたまにある。

ブラックリストに登録されると面倒くさいので、登録されないことが一番だが、登録されてしまった場合、IPを別のものに変更することで対処できる。

環境

  • Plesk 12.5
  • CentOS 6.2

対処

まず、前提として、以下の準備が完了しているものとする。
  • グローバルIPが複数ある
  • SPFレコードが複数のグローバルIPで設定している

手順1 送信メールモード


PleskのPanelにadminで入り、「ツールと設定」>「メールサーバ設定」と進み、送信メールモードを「ドメインIPアドレスから送信する」にする

手順2 送信元グローバルIP変更


# Plesk bin site -u <domain> -mail-service-ip <new-ip>
これで、送信元グローバルIPが変更され、ブラックリストが原因で送信できない問題を回避することができる。あとは、時間をかけて、原因の調査とブラックリストの削除申請を行えばよい。

これをやりすぎると、どのドメインがどのIPで送信しているのかがわからなくなる。そんな時は、以下を実行することで、ドメインとIPのマッピングを確認することができる。
# postmap -s /var/spool/postfix/plesk/sdd_transport_maps

参考

2017年7月8日土曜日

WordPressをApacheモジュールPHPセーフモードで動かした時の問題と対策

WordPressをApacheモジュールPHPセーフモードで動作させた場合、以下の問題が発生する。

  • ファイルアップロード
  • プラグイン自動アップデート(アップグレード)
  • プラグイン新規インストール
  • テーマ自動アップデート(アップグレード)
  • コアアップデート(アップグレード)

環境例

  • CentOS 6.2
  • Apache 2.2
  • PHP 5.3
  • WordPress 4.8

問題

厳密に言うと、WordPressファイルのユーザーとPHPを実行するユーザーが異なる場合、問題が発生する。

例えば、レンタルサーバーにFTPでWordPressのファイル群をアップロードする。
この時、ファイルのユーザーは当然のことながらFTPユーザーとなる。
その後、wp-config.phpを作成し基本設定。wp-admin/install.phpでインストール。
ここまでは問題ないが、ログイン後、ファイルアップロードすると以下のエラーが出る。


これは、WordPressを実行するApacheユーザーがFTPユーザーディレクトリに対し、ファイルを作成しようとし、書き込み権限(パーミッション)がないためにエラーとなっている。

これと同じ理由で、プラグインなどがインストールやアップデートできない。
権限の問題なので、権限をつければもちろん動作する。
例えば、ファイルアップロードであれば、wp-contentやuploadsなどにApacheが書き込める権限をつければ動作する。

しかし、WordPressの設定によっては、ディレクトリの作成方法が異なったり、そもそも、いつ、どこに、どんなディレクトリやファイルを作成するかなんてわからない。
プログラムを読めばわかると思うが、現実的ではない。

対策

以下htaccessをwp-adminに配下に設置し、WordPressをCGIとして動作させる。
(suEXECとしてCGIが動作することが前提。)

<FilesMatch "\.php$">
    AddHandler php-script .php
    Options +ExecCGI
</FilesMatch>

これで、WordPressを実行するユーザーとファイルのユーザーが同一になり、問題は解決する。

参考