2016年10月3日月曜日

nginx で ngx_http_geoip_module を利用して国別アクセス制御を行う

nginx Module ngx_http_geoip_module
http://nginx.org/en/docs/http/ngx_http_geoip_module.html

nginxのngx_http_geoip_moduleモジュールを利用して、国を指定してアクセス制御をします。
フィルタリング用のデータは、MaxMindのGeoIPオープンソースデータを利用します。
日本だけにコンテンツを配信したい、もしくは、特定の国からのアクセスを拒否したい、といったときに使えるとおもいます。
環境は、CentOS 6です。

以下、セットアップ方法です。

GeoIPデータベースを準備

GeoIPデータベースと更新コマンドをインストール。
# rpm -ivh http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
# yum install GeoIP geoipupdate
GeoIPデータベースファイルが設置されたことを確認。
# stat /usr/share/GeoIP/GeoIP.dat

nginxを準備

nginxをインストール。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# yum install nginx nginx-module-geoip
# nginx -v
nginx version: nginx/1.10.1
nginx設定パラメータにhttp_geoip_moduleが入っているか確認。GeoIPは、動的モジュールとして有効化できるようになっています。
# nginx -V
--with-http_geoip_module=dynamic
nginx動的モジュールの共有オブジェクトファイルが設置されているか確認。
# ls /etc/nginx/modules/
ngx_http_geoip_module-debug.so  ngx_http_geoip_module.so

nginx設定

カントリーコードはこちらのURLを参考にします。
http://dev.maxmind.com/geoip/legacy/codes/iso3166/

load_moduleを追記。modules/ngx_http_geoip_module.soを指定します。
events {} ブロックより上に記述するのがポイントです。
# emacs /etc/nginx/nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

load_module modules/ngx_http_geoip_module.so;

events {
    worker_connections  1024;
}

http {} ブロックを編集。
includeより上に記述します。
アメリカ(US)と日本(JP)からのアクセスを拒否する場合。
# emacs /etc/nginx/nginx.conf
    geoip_country /usr/share/GeoIP/GeoIP.dat;
    map $geoip_country_code $allowed_country {
        default yes;
        US no;
        JP no;
    }
日本(JP)と韓国(KR)からのアクセスを許可、他の国からのアクセスを拒否する場合。
# emacs /etc/nginx/nginx.conf
    geoip_country /usr/share/GeoIP/GeoIP.dat;
    map $geoip_country_code $allowed_country {
        default no;
        JP yes;
        KR yes;
    }

server {} ブロックを編集。
allowed_countryがnoだったらステータスコード403を返します。
# emacs /etc/nginx/conf.d/default.conf
    if ($allowed_country = no) {
        return 403;
    }


nginx再起動

nginxをリスタート、またはリロード。
# /etc/init.d/nginx restart
ブラウザなどでアクセスして確認しましょう。思い通りにアクセスコントロールできたでしょうか?

GeoIPデータベースを更新するには?

GeoIPデータベースをアップデートをするには、以下のコマンドを叩きます。定期実行するようにしましょう。
# geoipupdate -v

ELBの場合は?

AWSを使っている場合、ELBを前段に置く場合がほとんどだとおもいます。
ELBからのヘルスチェックでサーバに来るアクセスは、内部IPアドレスのため、国で判別することができません。そのため、VPC内のIPアドレスからアクセスが来た時には、制限をスルーするようにnginxを設定します。

以下の設定では、ELB経由のアクセスから、アクセス元IPアドレスを取得。ELBのヘルスチェックのアクセスを許可、日本からのアクセスを許可、ほかの国は拒否としています。
ついでに、アクセスログにGeo情報を出力するようにしています。



Top image from いらすとや

2016年9月7日水曜日

Rundeckをインストールして立ち上げる

http://rundeck.org/
高機能ジョブスケジューラの『Rundeck』を立ち上げるまでの記録です。
使い方は説明しません。
cronに飽きた。つらい。ジョブフローをうまく設定したい。もしくは、もっと高機能なことがしたい、といった欲求があるときに使われていようです。
ただ単に、時間通りバッチを動かしたい、ブラウザで操作できるGUIが欲しいときにはJenkinisで十分こと足りるとおもいます。なれている道具を使いましょう。
インストール環境は、CentOS 6です。

yumでJAVA OpenJDK 8と、Rundeckをインストール
# yum install java-1.8.0
# rpm -Uvh http://repo.rundeck.org/latest.rpm 
# yum install rundeck 
バージョンを確認
# java -version
openjdk version "1.8.0_101"
# yum list installed | grep rundeck
rundeck.noarch        2.6.9-1.21.GA     @rundeck-release-bintray
rundeck-config.noarch 2.6.9-1.21.GA     @rundeck-release-bintray
rundeck-repo.noarch   4-0               installed
Rundeckの設定ファイルを編集
ホスト名を設定。デフォルトではloaclhostとなる。
loaclhost -> myhost
# emacs /etc/rundeck/rundeck-config.properties
grails.serverURL=http://localhost:4440
->
grails.serverURL=http://myhost:4440
Rundeckのデーモンをスタート
# service rundeckd start
ログを確認
# tail -f /var/log/rundeck/rundeck.log /var/log/rundeck/service.log
しばらく待つと、アクセスできるようになります。
# curl -v 127.0.0.1:4440/user/login

ブラウザで開いてみましょう。
http://myhost:4440/
Username: admin
Password: admin
ログインできたら成功です。

参考資料

バックアップ

Backup and Recovery
http://rundeck.org/docs/administration/backup-and-recovery.html

データベース変更

デフォルトのデータベースだと、ジョブの数が多くなると負荷が上がるそうなので、MySQLを使ったりして解決できるそうです。

Setting up a RDB Datasource
http://rundeck.org/docs/administration/setting-up-an-rdb-datasource.html
Rundeckインストールと設定メモ - Qiita
http://qiita.com/nagase/items/70070d79454ac96aa5a5
Rundeck との闘争 - 日々是ウケ狙い
http://ndx.hatenablog.jp/entry/2016/02/12/225753

Slack連携

higanworks/rundeck-slack-incoming-webhook-plugin: A Rundeck Plugin for Slack Incoming-WebHook
https://github.com/higanworks/rundeck-slack-incoming-webhook-plugin
Rundeck Config・ジョブ通知・Mysql の設定編 - 僕とコードとブルーハワイ
http://equal-001.hatenablog.com/entry/2016/03/26/223000

その他

Rundeck - Google スライド
https://docs.google.com/presentation/d/1PKJ0iT-eS0oT4ILwJNvPkLFHWcqnRPsMY07kCb72tdk/edit#slide=id.p
cronからRundeckに移行するためのモチベーションが書かれています。

サーバが増えた時にインフラ担当者がやってきたこと - Qiita
http://qiita.com/hiracy/items/c8bfb69ff624d4b711e4
デプロイ集約サーバとしてRundeckを使っていて面白い。


Top image from http://rundeck.org/ by SimplifyOps

2016年9月6日火曜日

Google NEXT Tokyo 2016 メモと少しの感想

https://cloudplatformonline.com/NEXT2016-Tokyo.html
NEXT Tokyo 2016 - Google Cloud Platform カンファレンスに行ってきました。事例紹介を中心にみてきました。メモと若干の感想です。

♯キーノート

Room A
座席数600ほど。満員。

GCP リック・ハシューマン
・国内パートナーの紹介。
感想…少ない。20社ほど。
私見…国外サーバはレイテンシ高すぎて、エンドユーザーから繋いでもらうには遠すぎる。広告配信サーバや、低レイテンシが必要なAPIサーバとしては、遅すぎて使えない。今のところ計算用途でしか使えない。
・Q4で東京リージョンをローンチ。準備中。ハードはすでに持ってきた。

GCP コーネリアス・ウィルス
・目新しい情報なし。
・コストなどの話。
感想…AWS引き合いに出すのはダサい。

GCP マイルズ・ワード
・データセンターの話。ちゃんとやってます。と言う話。
・リージョン間のネットワークは専用線。
・Stackdriver(監視ツール)使ってね。
感想…AWS disダサい。とはいっても、観測しちゃった値だから、しようがないですね。。
・Datalabの話。データ分析のやり方の提案。

サイバーエージェント 長瀬
・AbemaTVでGCPを使っている。東京リュージョンに期待とのこと。
感想…リージョンの移行が大変そう。

GCP ナカイ
・Kubernetesの話。デプロイ簡単、複数リージョンでもうまくコントロールできるから使ってね。

リクルート 前田
・データ分析でGCPを使っている。とくにBigQuery。

GCP 塩入
・フレッシャーズ・サービス開始アナウンス。コンサルティング、サポートのようなもの。

♯データ&分析(Google の機械学習技術で実現する「賢い」アプリケーション)

Room A
座席数600ほど。8割くらいの入り。

Google 井上
・機械学習のチューニング、あらかじめうまくいくようにできてますよ。というお話。
・機械学習の事例紹介。クレジットカードの不正利用を検知。

オモロキ 鎌田
・Vision API 画像認識の事例。
不健全な投稿画像へのフィルタで利用。エンドユーザーが投稿するときに、都度、フィルタリングできるようになった。人力削除の仕事が40%減ってうれしい。
投稿のラベル付け、カテゴリ付けにも利用。

ブレインパッド 下田
・会社紹介

♯事例紹介(管理いらずの GCP でサービスに集中する方法)

Room C
座席数150 満員

オールアバウト 中村
・オンプレ環境からGCPへ移行した。
オンプレ運用でよくあるつらい運用から開放されたかった。
・なぜ移行したか?
AWSと比べて割安。台湾リュージョンのレイテンシ、ping 40msがあっても問題ないサービスを運用するため。
疑問…オンプレ環境のKVSではFusion ioMemory使っている図だったけど、GCPでのKVSはどうしている?GCEに載せている?それとも?

ノハナ 武市
・Parse.comからGCPへ移行した。
Parse.comサービス終了に伴ってGCPへ移行した。
KVSは、MongoDBを使っていた。GCPでもGCEにMongoDBを載せている。
・なぜ移行したか?
コストの安さ。GCPのデータ分析サービスを使ってみたい。

プレイド 後藤
・AWSからGCPへ移行した。
GCPではストリーム処理、BigTable、BigQueryをよくを使っている。
・なぜ移行したか?
CGPは、大量データ処理サービス、ロードバランサが強かった。
・苦労しているところ。
移行時のAWS、GCPデータセンター間のネットワークコスト。サポートのレスポンスが遅い。

要望…Cloud Datastoreの事例を聞いてみたい。

♯事例紹介(ゲーム業界の方必見!GCP のすごいところ)

Room B
座席数300満員

GCP 橋口
・GCP触ったことがない人?挙手1/3。

サイバーエージェント 白石
・2013からGAEを使っていた。
・デプロイ、開発環境の構築がつらかった。Kubernetesを使った開発スタイルをすすめている。
・チケット番号とポート番号をあわせて開発。
感想…えっ。。

Aiming 野下・芝男
・BigQueryを使ってデータ分析をしている。
社員がデータを見てくれない悩み。勉強会、社内報をがんばった。
・なぜGCPを使っている?
アジア向けサービス展開、GCE、CloudSQL。コスト安。
・国内ではAWSを使っている。GCP東京リュージョンがローンチしたら移行予定。

gumi 板倉
・BigQuery、GCSを使っている。
行動ログのデータ構造がよく変化するので、String JSONで保存。

感想…みなさんBigQuery、GCS使っていてツライところがあるはず。そこを聴きたい。
個人的にGCSを使っていて、たまにデータ入らないときがあった。つらい。

♯事例紹介(成長企業の GCP 活用法)

Room A
座席数600ほど。8割くらいの入り。

GCP 中澤
・GCP触ったことがある人?挙手2、3割くらい。

メルカリ 鶴岡
・BigQuery、Cloud Datalabを使っている。
・メルカリアッテ。すべてGAEを使っている。
グローバルにスケールしたい。JP、US、APAC。
アプリケーションのソースコードは共通。単一データベース。
疑問…データベースは何を使っている?
・サポート窓口が充実している。担当者がいて、すぐに回答が得られる。
疑問…会社によってGCPのサポートの質が違うような気がする。プレイドの事例では、サポートが遅いと言われていた。プランによって変わる?

ディー・エヌ・エー 小林
・AndAppでGCPを使っている。PC向けプラットフォーム。
いままでオンプレ中心だったけど、新しいテクノロジーを取り入れたい。現場のモチベーションが上がる。
インフラ運用負荷の小さいクラウドを使いたい。GAE Standard Environment。Firebase。BigQuery。
・今後、GAE Flexible Environmentに移行するか悩みどころ。

VOYAGE GROUP 西林
・Zucus AdNetwork、fluct、ECナビでGCP利用。
・BigQueryを使っている。投入データ量600GB/day。MySQLのデータも投入。
・BigQuery使用ログもBigQueryに入れる。使いすぎ検知。コストリミットを設定してアラート。
・月に一度くらいBigQuery調子悪いときがある。バッチをリトライできるようにしておく。安定している前提で設計しない。

感想…サーバサイドアプリケーションを、AWS・オンプレからGCPに移行する会社は、GCEを使って、新規サービスを立ち上げる会社はGAEを使う印象でした。

2016年9月1日木曜日

AWS Route53のDNSレコードをコマンドラインから操作する

Amazon Route 53 Authentication Tool for Curl
https://aws.amazon.com/developertools/Amazon-Route-53/9706686376855511

ダイナミックDNS(DDNS)を実装します。上記ページで公開されているスクリプト dnscurl.pl を使って、AWS Route53のDNSレコードを操作します。
Route53を操作するためのIAMポリシー・ユーザを作成して、DNSレコード更新サンプルを書いていきます。
環境は、CentOS 6です。

AWSコンソールから、Route53編集用のIAMポリシーをつくる

IAM Management Console Policies
https://console.aws.amazon.com/iam/home?region=ap-northeast-1#policies

IAMポリシー設定ページを開く -> Create Policy -> Create Your Own Policy
ポリシー名と、ポリシードキュメントを入力。
ここでは、IPアドレス 153.126.157.62 にRoute53の編集許可を与えます。

Policy Name: Route53-FullAccess-with-valid-IP-address
Policy Document:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:*"
            ],
            "Resource": [
                "*"
            ],
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "153.126.157.62/32"
                    ]
                }
            }
        }
    ]
}

IAMユーザをつくる

IAM Management Console Users
https://console.aws.amazon.com/iam/home?region=ap-northeast-1#users

IAMポリシー設定ページを開く -> Create New Users ->
Enter User Names: route53_user -> Create
Access Key ID: AAAAAAAAAAAAAAAAA
Secret Access Key: BBBBBBBBBBBBBBBBB
キーをメモしておく。

先ほど作ったポリシーをひもづける。
-> Attach Policy -> Route53-FullAccess-with-valid-IP-address

dnscurl.plの準備

必須ライブラリをインストール
# yum -y install perl-Digest-HMAC*
.aws-secretsファイルを準備
# emacs .aws-secrets
%awsSecretAccessKeys = (
    "my-aws-account" => {
        id => "AAAAAAAAAAAAAAAAA",
        key => "BBBBBBBBBBBBBBBBB",
    },
);

# chmod 600 .aws-secrets
dnscurl.plをダウンロード
# wget http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl
# chmod +x dnscurl.pl
# ./dnscurl.pl --debug

dnscurl.pl接続確認

# ./dnscurl.pl --keyname my-aws-account -- -H "Content-Type: text/xml; charset=UTF-8" https://route53.amazonaws.com/2010-10-01/hostedzone

<?xml version="1.0"?>
<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><HostedZones><HostedZone><Id>/hostedzone/HHHHHHHH</Id><Name>hitokoto.co.</Name><CallerReference>B56A5CA2-7F5D-5183-B014-94C24DE1720B</CallerReference><Config/></HostedZone></HostedZones><IsTruncated>false</IsTruncated><MaxItems>100</MaxItems></ListHostedZonesResponse>
-> Hosted Zone ID:HHHHHHHH をメモしておく。

ドメインからIPアドレスをひいてみる。
hostedzone/ 以下に Hosted Zone ID を指定。
name= に test.hitokoto.co. を指定。
./dnscurl.pl --keyname my-aws-account -- -H "Content-Type: text/xml; charset=UTF-8" "https://route53.amazonaws.com/2010-10-01/hostedzone/HHHHHHHH/rrset?name=test.hitokoto.co.&maxitems=1"

<?xml version="1.0"?>
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><ResourceRecordSets><ResourceRecordSet><Name>test.hitokoto.co.</Name><Type>A</Type><TTL>300</TTL><ResourceRecords><ResourceRecord><Value>153.126.157.62</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></ResourceRecordSets><IsTruncated>false</IsTruncated><MaxItems>1</MaxItems></ListResourceRecordSetsResponse>
指定したドメインのIPアドレスが返ってきました。

ドメインのIPアドレスを変更

test.hitokoto.co.のIPアドレスを変更します。153.126.157.62 -> 10.0.0.1
XMLファイルを用意。
$ emacs renew_ip_address_test.xml
IPアドレス変更を実行
# ./dnscurl.pl --keyname my-aws-account -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file ./renew_ip_address_test.xml https://route53.amazonaws.com/2013-04-01/hostedzone/HHHHHHHH/rrset

<?xml version="1.0"?>
<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><ChangeInfo><Id>/change/HHHHHHHH</Id><Status>PENDING</Status><SubmittedAt>2016-08-25T06:43:11.590Z</SubmittedAt></ChangeInfo></ChangeResourceRecordSetsResponse>
ステータス PENDING になりました。
数秒待つと設定が反映されます。

無事に設定されました。

2016年8月17日水曜日

Certbotクライアントで、Let’s Encrypt SSL証明書を自動更新する


https://certbot.eff.org
https://letsencrypt.org/getting-started/
Let’s Encryptのクライアントが新しくなり、Certbotとなりました。

環境:CentOS6, nginx

Certbotクライアントをインストール
$ git clone https://github.com/certbot/certbot
$ cd certbot/
$ sudo yum update openssl
$ sudo ./certbot-auto --debug

SSL証明書を発行
ドメイン:my.domain.com
ルートディレクトリ:/usr/share/nginx/html
メールアドレス:me@gmail.com
$ ./certbot-auto certonly --webroot \
-w /usr/share/nginx/html -d my.domain.com \
-m me@gmail.com \
--agree-tos
$ sudo ls /etc/letsencrypt/live/my.domain.com
cert.pem  chain.pem  fullchain.pem  privkey.pem

DHパラメータファイルを作成
$ sudo openssl dhparam 2048 -out /etc/letsencrypt/live/my.domain.com/dhparam.pem
これで、証明書ファイルが揃いました。

証明書の更新
いちど生成したSSL証明書は、コマンドオプションがファイルに記録されています。
renewと打つだけなので楽。

更新コマンド
$ ~/certbot/certbot-auto renew --force-renew 
cron設定
$ crontab -e

# Renew Let's Encrypt SSL
0 0 1 * *  /home/myuser/certbot/certbot-auto renew --force-renew && service nginx reload

2016年8月2日火曜日

pcregrepコマンドを使ってみる

pcregrepは、grepコマンドと同じように、文字パターンを指定してファイルを検索するコマンドです。
Perl5と互換性のある正規表現が書けます。Perlに慣れた人だったら重宝するかもしれないですね。

pcregrepをインストールする方法

pcregrepコマンドを内包しているPCREはメジャーバージョンが2つあって、ここではPCRE2を使うことにします。
コマンドは、pcre2grepになります。
古いバージョンだと、オプションが少ないし、セキュリティアップデートがあるので最新版を使うとよいです。

MacOSX
$ brew install pcre2
$ pcregrep -V
pcregrep version 8.39 2016-06-14
CentOS 6
# yum --enablerepo=epel install pcre2-tools
# pcre2grep -V
pcre2grep version 10.21 2016-01-12

以下、使い方の例です。

nginxのログから、IPアドレスの数でソート
# pcre2grep -o 'host:[0-9.]+' access.log | sort | uniq -c | sort -bg
    704 host:127.0.0.1
    736 host:5.104.241.192
    800 host:54.64.148.39
    843 host:5.104.241.190
    870 host:157.55.39.63
    888 host:40.77.167.67
    968 host:157.55.39.222
   1100 host:207.46.13.10
   1134 host:40.77.167.89
   1509 host:182.250.248.43
   2296 host:122.132.171.187
   2580 host:40.77.167.39
   3298 host:157.55.39.176
   3320 host:118.9.19.37
   7400 host:153.126.157.62
pcregrepは、grepより検索が早かったので、ログが大きい場合につかってみるといいかもしれません。 以下、timeをつけて時間を測った結果です。10倍近く差がでました。
grepの場合
# time grep -o -E 'host:[0-9.]+\s' access.log | sort | uniq -c | sort -bg
real 0m6.049s
user 0m5.973s
sys 0m0.077s

pcre2grepの場合
# time pcre2grep -o 'host:[0-9.]+(?=\s)' access.log | sort | uniq -c | sort -bg
real 0m0.759s
user 0m0.724s
sys 0m0.037s

特定の検索語を2つ以上指定して検索
# pcre2grep -o1 -o2 --om-separator=' ' '(iPad).*(reqtime:\d+\.\d+)' access.log
iPad reqtime:0.000
iPad reqtime:0.198
iPad reqtime:0.242
iPad reqtime:0.000
iPad reqtime:0.044

See also)
http://www.pcre.org/current/doc/html/pcre2grep.html

top image frpm Indi Samarajiva Mika Tennekoon Art

2016年7月15日金曜日

Fabricを使って、簡単にELB+EC2環境にローリングデプロイ

Python製のプロビジョニングツールであるFabricを使って、ELB + EC2環境にアプリケーションをローリングデプロイします。
ELBから1台ずつEC2インスタンスを切り離して、アプリケーションをデプロイしてリスタート、終わったら、ELBにEC2インスタンスを追加します。
なぜ、Fabricを使ったかというと、ほかのデプロイ自動化ツール、たとえばCapistranoやAnsibleだと、ELBのAPIを叩く処理を書くのがちょっと面倒そうだったからです。Fabricなら使いやすそうなPythonライブラリがあり、簡潔に書けるな、と考えたからです。
以下のFabricスクリプトは、ロールバックやヘルスチェックなどの処理を書いていません。もうちょっと高級なことがしたかったら、他の方法を考えたりするといいと思います。
※ ELBは、Classic load balancerを使っています。

Fabricのインストール
環境:Mac OS X
$ sudo easy_install fabric
$ sudo pip install boto
$ fab --version
Fabric 1.11.1
Paramiko 1.17.1
Fabricスクリプト
実行結果

Top image from msburrows roll

2016年7月1日金曜日

HTTPヘッダーからPhusion Passengerのバージョンを隠す

Chrome DevToolsで自分のサイトを見ていたところ、Sinatraで作ったウェブアプリのHTTPヘッダーに、Phusion Passengerのバージョン情報が出ていて、気になってしまったので。消してみました。
こんな感じです。
Server:nginx + Phusion Passenger 5.0.29
X-Powered-By:Phusion Passenger 5.0.29
nginxの設定に以下の記述を入れてPhusion Passengerのバージョン情報を消しました。
情報を上書きすれば良いです。
server {
    listen 80;
    server_name rapper.hitokoto.co;

    more_set_headers 'Server: nginx';
    more_set_headers 'X-Powered-By';

top image from Saïda ...hide...

2016年6月21日火曜日

WordPressの応答をVarnishで高速化

https://varnish-cache.org/
Varnishを使ってWordPressをキャッシュする方法です。
Varnish port 80 -> nginx port 8080 -> php-fpm -> WordPress という構成にします。

Varnishは、いままで使ったことがなかったので、導入だけでも触っておこうと考えて、よくありそうなユースケースとして、ウェブメディアの前段にキャッシュを置く、という使い方をしてみました。
ほかに、外部サービスのAPIが不安定だったりする場合、間にVarnishを挟んで、可用性とパフォーマンスを確保する使い方もあるようです。
External API Caching with Varnish & Nginx http://blog.runnable.com/post/144975295096/external-api-caching-with-varnish-nginx


以下、既に、WordPressが立っているという状況で進めていきます。
環境は、CentOS 6.7です。

Varnishをインストール
$ rpm --nosignature -i http://repo.varnish-cache.org/redhat/varnish-4.1/el6/noarch/varnish-release/varnish-release-4.1-2.el6.noarch.rpm
$ yum install varnish
# varnishd -V
varnishd (varnish-4.1.2 revision 0d7404e)
Copyright (c) 2006 Verdens Gang AS
Copyright (c) 2006-2015 Varnish Software AS[

nginxの設定ファイルを編集
nginxの設定。ポート8080番で待ち受けるようにします。
# vim /etc/nginx/conf.d/default.conf
listen       80;
->
listen       8080;
https://gist.github.com/takeshiyako2/5cd855e9d3bc2e55b8cb5d5f70d1ff6e#file-default-conf

Varnishの設定ファイルを編集
Varnishのポートの設定。80番で待ち受けるようにします。
# vim /etc/sysconfig/varnish
VARNISH_LISTEN_PORT=6081
->
VARNISH_LISTEN_PORT=80
https://gist.github.com/takeshiyako2/5cd855e9d3bc2e55b8cb5d5f70d1ff6e#file-varnish

VCL(Varnish Configuration Language)の設定。
以下、設定のポイントです。
・バックエンドをnginx(127.0.0.1:8080)に
・localhostからのパージリクエストを許可(ここでは、全てのキャッシュをパージするBANを設定)
・WordPress管理者ページをキャッシュしないように
・コンテンツでは、クッキーを無視して、キャッシュが効くように
# vim /etc/varnish/default.vcl

以上で設定は完了です。

nginxとVarnishをリロード(or リスタート)
# service nginx restart
# service varnish restart
ちゃんとキャッシュされているでしょうか?
Varnishにキャッシュされたコンテンツがnginxログに出なくなっているとおもいます。
キャッシュが破棄される時間、TTLは、/etc/sysconfig/varnishで設定されています。必要に応じて編集すると良いでしょう。デフォルトでは120秒です。

キャッシュをパージするには?
curlでVarnishのAPIを叩きます。
以下のコマンドで、全てのキャッシュをパージします。
# curl -X BAN -v http://localhost/
細かくパスを指定してパージもできます。詳しくは公式ドキュメントを参照してください。
Purging and banning — Varnish version 4.1.2 documentation https://www.varnish-cache.org/docs/4.1/users-guide/purging.html

ベンチマーク
Varnishなし。
# ab -c 100 -n 10000 http://192.168.33.10/
Server Software:        nginx/1.10.1
Server Hostname:        192.168.33.10
Server Port:            80

Document Path:          /
Document Length:        10400 bytes

Concurrency Level:      100
Time taken for tests:   222.524 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      106320000 bytes
HTML transferred:       104000000 bytes
Requests per second:    44.94 [#/sec] (mean)
Time per request:       2225.244 [ms] (mean)
Time per request:       22.252 [ms] (mean, across all concurrent requests)
Transfer rate:          466.59 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   4.7      0      51
Processing:    83 2218 452.3   2118    5395
Waiting:       31 2070 435.9   1977    5210
Total:         83 2218 451.4   2119    5395

Percentage of the requests served within a certain time (ms)
  50%   2119
  66%   2250
  75%   2347
  80%   2420
  90%   2731
  95%   3164
  98%   3642
  99%   3945
 100%   5395 (longest request)
Varnishあり。
Server Software:        nginx/1.10.1
Server Hostname:        192.168.33.10
Server Port:            80

Document Path:          /
Document Length:        10465 bytes

Concurrency Level:      100
Time taken for tests:   1.546 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      107936450 bytes
HTML transferred:       104650000 bytes
Requests per second:    6466.26 [#/sec] (mean)
Time per request:       15.465 [ms] (mean)
Time per request:       0.155 [ms] (mean, across all concurrent requests)
Transfer rate:          68158.75 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    7   2.6      6      19
Processing:     3    8   2.7      8      27
Waiting:        0    5   2.9      5      18
Total:          9   15   3.4     15      29

Percentage of the requests served within a certain time (ms)
  50%     15
  66%     16
  75%     17
  80%     17
  90%     20
  95%     22
  98%     26
  99%     27
 100%     29 (longest request)
Varnishなし 44.94 req/s、Varnishあり 6466.26 req/s。
Varnishありの転送サイズが若干多いのは、ヘッダーが付与されているからかなと。

まとめ
Varnishを導入して、WordPressの応答を高速化しました。
初歩的な使いかたはできたかな?と思います。
今回はやりませんでしたが、ディレクトリ単位、ファイル単位といった細かい粒でキャッシュのパージができます。このため、応用次第で、様々なサイトにキャッシュを導入できたりしそうです。サーバサイドアプリの応答が遅くて、そこそこアクセスがあるサイトとかだったら、導入を検討してみる価値があるかもしれません。
ちなみに。単純に、WordPressのキャッシュを高速に返したいのであれば、nginxのproxy_cacheで十分だと思います。。


top image from https://varnish-cache.org/

2016年5月19日木曜日

nginxモジュールのngx_dynamic_upstreamを使えるようにする


nginxモジュールの ngx_dynamic_upstream を試してみたかったので、使えるようにしてみました。
nginxをyumからインストールして、ユーザ追加などの雑用を任せて、そのあとにソースからコンパイルします。
モジュールの使い方については、ここでは説明しません。
環境は、CentOS 6.7です。

nginxの最新版をyumからインストール。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
# yum -y install nginx
オプションを確認します。
# nginx -V
nginx version: nginx/1.10.0
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --add-dynamic-module=njs-1c50334fbea6/nginx --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic'
nginxのビルドに必要なライブラリをインストール。
# yum install pcre-devel zlib-devel openssl-devel libxml2-devel libxslt-devel gd-devel perl-ExtUtils-Embed GeoIP-devel
nginxをダウンロード。
$ cd /usr/local/src
$ git clone https://github.com/cubicdaiya/ngx_dynamic_upstream.git
$ wget http://nginx.org/download/nginx-1.10.0.tar.gz
$ tar zxvf nginx-1.10.0.tar.gz
$ cd nginx-1.10.0
configureに、先ほどのオプションを全部追加すると、以下のエラーが出ます。
adding module in njs-1c50334fbea6/nginx
./configure: error: no njs-1c50334fbea6/nginx/config was found
これは、nginx with HTTP JavaScript moduleです。オプションから --add-dynamic-module=njs-1c50334fbea6/nginxを外してconfigureを実行します。
必要な場合は、https://github.com/nginx/njs を準備しましょう。

configureを実行。
$ ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' --add-module=/usr/local/src/ngx_dynamic_upstream
インストール。
# make
# make install
# nginx -V
nginx version: nginx/1.10.0
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' --add-module=/usr/local/src/ngx_dynamic_upstream
ngx_dynamic_upstreamが有効になりました。

Top image from James Morris upstream - we're on the same page

2016年5月12日木曜日

MySQL HandlerSocketのレイテンシを監視するNagiosプラグイン

https://github.com/takeshiyako2/nagios-check_handlersocket_latency

MySQL HandlerSocketのレイテンシを監視するプラグインを書きました。
簡単な使い方。
% check_handlersocket_latency -w [waring seconds] -c [critical seconds] -host [hostname]
% check_handlersocket_latency -w 0.70 -c 1.00 -host localhost
デフォルトでは、mysqlデータベースのuserテーブルから、Hostがlocalhostのレコードを取り出して、時間を測っています。
デフォルト値は以下のようにしています。
host: localhost
port: 9998
database: mysql
table: user
index: PRIMARY
columns: Host
value: localhost
これは、オプションで変更可能です。
たとえば、以下の様なテーブルを用意したときは、このようになります。
mysql> use sampledb;
mysql> CREATE TABLE personal(id int PRIMARY KEY, name varchar(20));
mysql> INSERT INTO personal (id,name) VALUES (100, "test");
$ ./check_handlersocket_latency -w 0.005 -c 0.020 -host localhost -database sampledb -table personal -columns id -value 100
OK: HandlerSocket read response in 0.001996 s|latency_seconds=0.001996

2016年5月10日火曜日

GitとGoの最新バージョンとVulsをインストール CentOS


https://github.com/future-architect/vuls
Vulsをgo getでインストールしようとしたら、gopkg.in/gomail.v2のインストールで止まってしまった。
# go get -v github.com/future-architect/vuls
Fetching https://gopkg.in/gomail.v2?go-get=1
Parsing meta tags from https://gopkg.in/gomail.v2?go-get=1 (status code 200)
get "gopkg.in/gomail.v2": found meta tag main.metaImport{Prefix:"gopkg.in/gomail.v2", VCS:"git", RepoRoot:"https://gopkg.in/gomail.v2"} at https://gopkg.in/gomail.v2?go-get=1
gopkg.in/gomail.v2 (download)
調べたところ、Gitのバージョンが古いので、動かなかったことがわかった。Gitを最新バージョンにしたらうまく動いてくれた。
ここでは、GitとGoの最新バージョンとVulsをインストールするところを説明します。具体的なVulsの使いかたについては、説明しません。

環境は、CentOS 6.7です。
# cat /etc/redhat-release
CentOS release 6.7 (Final)
Git最新版をインストール
https://www.kernel.org/pub/software/scm/git/
yumでインストールした既存のGitを削除。
# git --version
git version 1.7.1
# yum -y remove git
必須ライブラリと、Git最新バージョンをインストール。
# yum -y install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker
# wget https://www.kernel.org/pub/software/scm/git/git-2.8.2.tar.gz
# tar zxvf git-*.tar.gz
# cd git-*
# ./configure --prefix=/usr/local
# make prefix=/usr/local all
# make prefix=/usr/local install
# export PATH=/usr/local/bin:$PATH
# git --version
git version 2.8.2
Go最新版をインストール
https://golang.org/dl/
# wget https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz
# tar -C /usr/local -xzf go1.6.2.linux-amd64.tar.gz
# export PATH=$PATH:/usr/local/go/bin
# go version
go version go1.6.2 linux/amd64
# export GOPATH=$HOME/work
# mkdir work
Vulsをインストール
# go get -v github.com/future-architect/vuls
# ~/work/bin/vuls help
うまく動いたでしょうか?

Top photo from Alan L go!

2016年4月22日金曜日

logrotateを1時間ごとに実行する

logrotateの最新版をインストールして、logrotateを1時間ごとに実行させます。
たとえば、アクセスが多いサーバだとnginxのアクセスログなどが膨らんで、1日1回のローテートと圧縮では、ディスク容量が足りなくなったりすることが、よくあると思います。また、ログの行数が多いと、生ログを調査するときにgrepするのが辛くなってきたりします。そこで、1時間毎にログをローテートして小分けにする方法として、logrotateの最新版でサポートされている hourly オプションを使ってローテートしてみました。

以下、セットアップ方法です。
環境は、CentOS 6.7です。

現在のlogrotateバージョンを確認。
$ logrotate
logrotate 3.7.8 - Copyright (C) 1995-2001 Red Hat, Inc.
現在の各種ライブラリをインストール。rpmbuildで最新版logrotateのrpmファイルを作ります。
$ sudo yum groupinstall "Development Tools" 
$ sudo yum install popt-devel rpmdevtools
$ rpmdev-setuptree
$ wget https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.9.1.tar.gz
$ cp logrotate-3.9.1.tar.gz rpmbuild/SOURCES/
$ tar -zxvf logrotate-3.9.1.tar.gz
$ cp logrotate-3.9.1/logrotate.spec rpmbuild/SPECS
$ rpmbuild -bb rpmbuild/SPECS/logrotate.spec
logrotate最新版をインストール。
$ cd rpmbuild/RPMS/x86_64/
$ sudo rpm -Uvh logrotate-3.9.1-1.x86_64.rpm
# logrotate --version
logrotate 3.9.1
nginxの設定ファイルを編集。
# emacs /etc/logrotate.d/nginx
/var/log/nginx/*.log {
        hourly
        missingok
        rotate 240
        compress
        delaycompress
        create 644 nginx adm
        dateext
        dateformat -%Y%m%d%H
        sharedscripts
        postrotate
                [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
        endscript
}
logrotateのcronの設定を、dailyからhourlyに移動。
# mv /etc/cron.daily/logrotate /etc/cron.hourly/
しばらく待って、ログを見てみましょう。
# ls -al /var/log/nginx | grep access.log
-rw-r--r--   1 nginx adm    302091 Apr 22 14:12 access.log
-rw-r--r--   1 nginx adm    114085 Apr 22 12:01 access.log-2016042212.gz
-rw-r--r--   1 nginx adm    149606 Apr 22 13:01 access.log-2016042213.gz
-rw-r--r--   1 nginx adm   1240079 Apr 22 14:01 access.log-2016042214
ちゃんとローテートされていますね。

Top pic from Ralph Arvesen Star trails while watching Perseid meteor shower

2016年4月18日月曜日

Goofysを使ってS3をマウント

Amazon S3のバケツをマウントしてファイル操作できるようにします。
S3をマウントする方法としてメジャーなs3fs-fuseというプロダクトがありますが、パフォーマンスが良くないです。色々調べたところ、よりパフォーマンスを重視した、Goofysというプロダクトがあったので、試してみました。
POSIXの規格を厳密に実装せず、パフォーマンスを重視した実装になっているそうです。

以下、セットアップ方法です。

環境
AMI: Amazon Linux AMI 2016.03.0 (HVM), SSD Volume Type - ami-f80e0596 
Type: c4.large

S3でバケツを作成しておきます。
バケツ名は my-goofys-bucket としました。

Goofysをセットアップ
Goとfuseをインストール。
$ sudo yum install -y golang fuse
Goofysをインストール。
$ sudo wget https://github.com/kahing/goofys/releases/download/v0.0.5/goofys -P /usr/local/bin/
$ sudo chmod 755 /usr/local/bin/goofys
$ /usr/local/bin/goofys -h
AWS CLIを設定。
$ aws configure 
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: 
Default output format [None]:
マウントするディレクトリを作成。
$ mkdir ~/mountpoint
ユーザのidを確認。
$ id
uid=503(yako) gid=503(yako) groups=503(yako),10(wheel)
マウント。ここでは、オプションで、allow_other, uid, gidを付けます。現在のユーザ、rootユーザ以外でもディレクトリのファイルを見ることができるようにします。
$ sudo /usr/local/bin/goofys my-goofys-bucket ~/mountpoint -o allow_other,--uid=503,--gid=503
Goofysプロセスが立っているのがわかります。
$ ps auxf | grep goofys
yako      3624  0.0  0.0 110456  2196 pts/1    S+   05:41   0:00              \_ grep --color=auto goofys
yako      3586  0.0  0.4 223236 15592 ?        Ssl  05:38   0:00 /usr/local/bin/goofys my-goofys-bucket /home/yako/mountpoint
ログを確認。
# grep goofys /var/log/messages
Apr 15 05:38:37 ip-172-31-10-234 /usr/local/bin/goofys[3586]: s3.INFO Switching from region 'us-west-2' to 'ap-northeast-1'
Apr 15 05:38:37 ip-172-31-10-234 /usr/local/bin/goofys[3586]: main.INFO File system has been successfully mounted.
テストファイルを置いてみます。
$ touch mountpoint/test.txt
AWS CLIから確認。
$ aws s3 ls s3://my-goofys-bucket
2016-04-15 05:38:53          0 test.txt
ちゃんとファイルが設置されていますね。

アンマウントするには?
rootユーザでアンマウントを実行します。
ディレクトリを使用していないか確認。
$ sudo lsof | grep mountpoint
アンマウント実行
$ sudo umount /home/yako/mountpoint

リブート後も自動マウントされるようにするには?
fstabに追記します。
$ sudo vi /etc/fstab 
/usr/local/bin/goofys#my-goofys-bucket  /home/yako/mountpoint        fuse     _netdev,allow_other,--uid=503,--gid=503    0       0
rootユーザにAWS CLIを設定
$ sudo su 
# aws configure
リブート。
$ sudo su  reboot
起動したら、ファイルを確認してみます。
$ ls mountpoint/
test.txt
無事、マウントされていました。

Goofysが『やらないこと』
・S3に載せたファイルに任意のContent-Typeを設定しない。すべて、 binary/octet-stream になる。
※最新版で対応したそうです。https://github.com/kahing/goofys/releases
・S3のアクセスコントロールの設定をしない。
もし、コントロールしたいときは、別のスクリプトや、AWS Lambdaを回して設定しましょう。

まとめ
Goofysを使って、S3をマウントしました。
1ヶ月くらい使っていますが、とくに問題は起きていないです。
S3にファイルを集中して損失リスクの責任を持たせるので、個人的には、自前で分散ファイルシステムのNFSを組むより安心感があります。
AWSでは、Amazon EFSを準備中で、プレビュー版を提供中です。リリースされたら、くらべてみたいですね。


Top photo from mezosurfer Goofy Golf Sign

2016年4月4日月曜日

kernelアップデート時に/bootの容量が足りなかったときの対処方法

対象は、CentOSです。yumを使ってカーネルをアップデートしようとしたら。/bootの空きスペースがなくてエラーとなってしまいました。package-cleanupを実行して古いカーネルを削除したら、うまくいきました。これは、そのときのメモです。
# yum update kernel kernel-devel
~~~
Transaction Check Error:
  installing package kernel-2.6.32-573.22.1.el6.x86_64 needs 20MB on the /boot filesystem

Error Summary
-------------
Disk Requirements:
  At least 20MB more space needed on the /boot filesystem.
-> /bootの容量が足りないのでエラーになった。
# df
Filesystem     1K-blocks     Used Available Use% Mounted on
/dev/vda3       38988720 17003752  20003268  46% /
tmpfs            1961360        0   1961360   0% /dev/shm
/dev/vda1          95425    81342      8944  91% /boot
-> 確かに、空きがほとんどないです。

package-cleanupを使えるようにします。
$ yum install yum-utils
古いカーネルを削除。
# package-cleanup --oldkernels
カーネルをアップデート。
# yum update kernel kernel-devel
リブートして、カーネルの更新完了です。
# reboot

Top image from aimee rivers CLEAN ME!

2016年3月18日金曜日

Python + Django + uWSGIをセットアップ

Python + Djangoの最新版(Python 3.5.1、Django 1.9.4)を使って動かしてみます。簡単なDjangoのサンプルプログラムを用意して、ブラウザで見れるようにします。
WebサーバにはuWSGI + nginxをセットアップします。uWSGIは、Upstartを使ってデーモンを起動するようにします。
OSは、EC2の Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-a21529cc です。

個人的に、PythonでWebサービスを書いたことがなかったので、どういうふうになっているのか興味があって調べてみた。というのが今回の動機です。
以下、セットアップ方法です。ステップバイステップで説明していきます。

Python 3.5をインストール
システムにインストールします。
複数バージョンのPythonを扱うpythonzというプロダクトもありますが、ここでは使わないでおきます。
fkrull's repository (https://launchpad.net/~fkrull/+archive/ubuntu/deadsnakes)を使います。
$ sudo add-apt-repository ppa:fkrull/deadsnakes
$ sudo aptitude update
$ sudo apt-get install python3.5 python3.5-dev python3-pip python-pip
/usr/bin/pythonへシンボリックリンクを貼る。
$ sudo rm /usr/bin/python
$ sudo ln -s /usr/bin/python3.5 /usr/bin/python
バージョン確認。
$ python --version
Python 3.5.1
$ pip --version
pip 1.5.4 from /usr/lib/python3/dist-packages (python 3.5)

Djangoのサンプルアプリを準備
テスト用ディレクトリを作って、DjangoとuWSGIをインストール。
$ mkdir test-app
$ cd test-app/
$ emacs requirements.txt
Django==1.9.4
uWSGI==2.0.12
$ sudo pip install -r requirements.txt
テスト用プロジェクトを作成。
$ django-admin startproject myweb
サンプルアプリを作成。
$ cd myweb
$ python manage.py startapp hello
$ emacs myweb/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',
]

$ emacs hello/views.py
from django.http import HttpResponse
def main(request):
        return HttpResponse("Hello!")

$ emacs myweb/urls.py
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^hello/', 'hello.views.main'),
]
マイグレーションを実行。
$ python manage.py migrate
簡易Webサーバを立てて、HTTPでアクセスしてみます。
$ python manage.py runserver 0.0.0.0:8000
$ curl http://127.0.0.1:8000/hello/
Hello!
サンプルアプリの準備ができました。

nginxのインストール
nginxをインストールします。
UNIXドメインソケットを通じてWSGIへ繋ぐように設定します。
uwsgi_passは都合よく変更してください。
$ sudo aptitude install nginx-full
$ sudo emacs /etc/nginx/sites-available/default
server {
    listen 80;
    location / {
        include uwsgi_params;
        uwsgi_param Host $host;
        uwsgi_param X-Real-IP $remote_addr;
        uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
        uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto;
        uwsgi_pass unix:/home/yako/test-app/myweb/uwsgi.sock;
    }
}

$ sudo service nginx restart

uWSGI設定ファイルを編集
uWSGIは、スレッドをサポートしていますが、スレッドを有効にすると graceful restart がうなく動かなかったので、ここでは有効にしませんでした。
$ cd /home/yako/test-app/myweb
$ emacs uwsgi_staging.ini
[uwsgi]
base = /home/yako/test-app/myweb
chdir           = %(base)
socket          = %(base)/uwsgi.sock
pidfile         = %(base)/exchange-server.pid
touch-reload    = %(base)/uwsgi_touch_reload.trigger
touch-logreopen = %(base)/uwsgi_touch_logreopenr.trigger
daemonize       = %(base)/manage.log
module = myweb.wsgi
master = True
vacuum = True
processes = 4
max-requests = 1000
harakiri = 10
chmod-socket = 666
die-on-term = true

Upstart設定ファイルを編集
uWSGIスタート前に、touch-logreopenファイルを削除します。このファイルが存在する状態で、uWSGIをスタートすると、touch-logreopen機能が動きませんでした。
(さらに、uWSGIをreloadするとtouch-logreopen機能が動かなくなりました。良い回避方法があったら教えてください。)
Djangoでファイル名に日本語が入っているファイルのアップロードできるように、export LC_ALL=en_US.UTF-8を入れておきます。
$ sudo emacs /etc/init/uwsgi.conf
description "myweb"

start on runlevel [2345]
stop on runlevel [!2345]

setuid yako
setgid yako

expect daemon
respawn

pre-start script
    # remove touch files
    base=/home/yako/test-app/myweb/
    touch_reload_file=$base/uwsgi_touch_logreopenr.trigger
    touch_logreopen_file=$base/uwsgi_touch_logreopenr.trigger
    if [ -e $touch_reload_file ]; then
        rm $touch_reload_file
    fi
    if [ -e $touch_logreopen_file ]; then
        rm $touch_logreopen_file
    fi
end script

script
    export LC_ALL=en_US.UTF-8
    chdir /home/yako/test-app/myweb
    exec uwsgi --ini uwsgi_staging.ini
end script

サンプルアプリをスタート
$ sudo service uwsgi start
プロセスチェック。
$ ps auxf | grep uwsgi
yako     16870  0.0  0.0  10460   936 pts/0    S+   06:02   0:00  |           \_ grep --color=auto uwsgi
yako     16850  2.8  0.6  97708 26948 ?        S    06:02   0:00 uwsgi --ini uwsgi_staging.ini
yako     16853  0.0  0.6 318904 23324 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16854  0.0  0.6 318904 23136 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16855  0.0  0.6 318904 23336 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
yako     16859  0.0  0.6 318904 23324 ?        Sl   06:02   0:00  \_ uwsgi --ini uwsgi_staging.ini
ログを確認。
$ tail -f /home/yako/test-app/myweb/manage.log
HTTPでアクセスしてみます。
curl -> nginx -> uWSGI -> Django と、繋がる形になります。
$ curl 127.0.0.1/hello/
Hello!
うまくいきました。

uWSGIの運用
ログファイルをローテートするときは、touch-logreopenで設定したファイルを更新します。
$ touch /home/yako/test-app/myweb/uwsgi_touch_logreopenr.trigger


See also)
Django Under The Hood Photo by Bartek Pawlik
https://www.flickr.com/photos/137962885@N08/22793957097/in/photolist-AJdZji-rJ1Av8-9RzYXi-cabjcj-Dgv6rN-rKK85W-s3kxtP-r6x6Gx-5KNeRh-s3kxre-rKK8dG-s13tgs-5kYmmW-5jMxyf-6cBmZt-djfBr4-6eVGGw-ekyaUg-dUbX9V-4ptVd-B9As9d-81dkcd-4JvxJL-B9AB59-5Sz4Gp-s3cT3G-r6x6Mn-r6kjvU-rKK8p3-rKTa8D-s3kxt8-r6kjou-rJ1Anc-r6x6Ra-5jDb1t-ASV1Fw-dKUbwX-BhKcMS-s3kxvc-s3cSwb-rKLg1L-rJ1ABa-s3gZ3P-rJ1A7c-s3ky8e-s13tq5-rKK8aA-FEgA9-cYohhb-AnvLtC
The Web framework for perfectionists with deadlines | Django
https://www.djangoproject.com/
The uWSGI project — uWSGI 2.0 documentation
http://uwsgi-docs.readthedocs.org/en/latest/index.html
Running uWSGI via Upstart — uWSGI 2.0 documentation What is –die-on-term?
http://uwsgi-docs.readthedocs.org/en/latest/Upstart.html#what-is-die-on-term
Managing the uWSGI server — uWSGI 2.0 documentation
http://uwsgi-docs.readthedocs.org/en/latest/Management.html#signals-for-controlling-uwsgi
NGINX as an Application Gateway with uWSGI and Django
https://www.nginx.com/resources/admin-guide/gateway-uwsgi-django/
EC2上で Django + Nginx + uWSGI を試す - Qiita
http://qiita.com/melos/items/91dc3b45a589ea5a369b

2016年2月1日月曜日

MacにMackerelをインストールして負荷を可視化する (OS X El Capitan‎)

サーバのリソース可視化サービスのひとつであるMackerelを使って、Macの負荷をグラフで見れるようにします。エージェントプログラムをMacにインストールして、データを送信するところまでやってみます。
上のグラフはCPUの使用状況をMackerelで見れるようにしたときのキャプチャです。他にも、情報が色々取れます。これらは、Macのアクティビティモニタでも取れる情報ではありますが、Mackerelだと無料枠で、1日分の情報を保存しておけるので、より大きな範囲の情報が見ることができます。たとえば、午前と午後でのPCの使いかたの違いなど、面白い発見があるかもしれません。

以下、Mackerelのエージェントソフト(mackerel-agent)のインストール方法です。
OSは、OS X El Capitanです。
brewをあらかじめ使えるようにしてある前提です。

brewでmackerel-agentをインストール
$ brew tap mackerelio/mackerel-agent
==> Tapping mackerelio/mackerel-agent
Cloning into '/usr/local/Library/Taps/mackerelio/homebrew-mackerel-agent'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 8 (delta 1), reused 4 (delta 0), pack-reused 0
Unpacking objects: 100% (8/8), done.
Checking connectivity... done.
Tapped 2 formulae (34 files, 140K)
$ sudo brew install mackerel-agent
==> Installing mackerel-agent from mackerelio/homebrew-mackerel-agent
==> Downloading https://github.com/mackerelio/mackerel-agent/releases/download/v0.27.1/mackerel-agent_darwin_amd64.zip
==> Downloading from https://github-cloud.s3.amazonaws.com/releases/19845168/6034e660-b61d-11e5-8055-d1fd7f0c32d6.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOV
######################################################################## 100.0%
==> Caveats
You must append `apikey = {apikey}` configuration variable to /usr/local/etc/mackerel-agent.conf
in order for mackerel-agent to work.

To have launchd start mackerelio/mackerel-agent/mackerel-agent at login:
  ln -sfv /usr/local/opt/mackerel-agent/*.plist ~/Library/LaunchAgents
Then to load mackerelio/mackerel-agent/mackerel-agent now:
  launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mackerel-agent.plist
Or, if you don't want/need launchctl, you can just run:
  mackerel-agent -conf /usr/local/etc/mackerel-agent.conf
==> Summary
  /usr/local/Cellar/mackerel-agent/0.27.1: 4 files, 8.1M, built in 6 seconds
※ 以下のエラーが出るときは、Xcodeのライセンスに同意する必要があります。
You have not agreed to the Xcode license agreements, please run 'xcodebuild -license' (for user-level acceptance) or 'sudo xcodebuild -license' (for system-wide acceptance) from within a Terminal window to review and agree to the Xcode license agreements.
Error: Failure while executing: /usr/bin/otool -L /usr/bin/install_name_tool
表示に従ってコマンドを打ちます。
$ xcodebuild -license
ライセンスが表示されます。[q]で抜けます。
By typing 'agree' you are agreeing to the terms of the software license agreements. Type 'print' to print them or anything else to cancel, [agree, print, cancel] agree
ライセンスに同意するか聞かれるので [agree] と入力してEnter。
これでもう一度、mackerel-agentをインストールしてみてください。

設定ファイルを編集
MackerelのAPIキーをコピーして、apikeyを入力します。
$ sudo emacs /usr/local/etc/mackerel-agent.conf
apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

deamon化して自動で起動
launchctlの設定ファイル(plistファイル)が添付されているので、これを~/Library/LaunchAgentsディレクトリに配置。
launchctl loadでサービスを開始します。
$ sudo ln -sfv /usr/local/opt/mackerel-agent/*.plist ~/Library/LaunchAgents
$ sudo launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mackerel-agent.plist
プロセスを確認します。
$ sudo launchctl list | grep mackerel
8563 0 homebrew.mxcl.mackerel-agent
エラーログを確認します。
$ tail -f /usr/local/var/log/mackerel-agent.log
※以下の様なエラーが出ているときは、データの送信に失敗しています。設定ファイルのapikeyなどを見なおしましょう。
2016/01/29 11:20:15 INFO 
Starting mackerel-agent version:0.27.1, rev:06d85e6, apibase:https://mackerel.io 2016/01/29 11:20:15 WARNING API error. status: 401, msg: status code is not 200 2016/01/29 11:20:15 CRITICAL
Failed to prepare host: Failed to find this host on mackerel (You may want to delete file "Library/mackerel-agent/id" to register this host to an another organization): API error. status: 401, msg: status code is not 200
ストップするには以下のコマンドを打ちます。
$ sudo launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.mackerel-agent.plist
$ sudo launchctl stop homebrew.mxcl.mackerel-agent
しばらくすると、Mackerelのコンソール上に情報が表示されてきます。
うまく表示されたでしょうか?

まとめ
MacにMackerelエージェントをインストールして、Mackerelでリソース情報を見れるようにしました。brewを使ってインストールできるので、Mackerelエージェント起動までの手順が少なくてよかったです。
MackerelのAPIの仕様がわかりやすいので、たとえば、室温や湿度センサーの情報をMacに入力して、Mackerelで見たりしたら面白そうだと思いました。

See Also
mackerel-agentをMacに簡単にインストール可能になりました、ホスト数の表示の改善しました・ほか - Mackerel ブログ #mackerelio
http://blog-ja.mackerel.io/entry/2015/02/27/141105
A launchd Tutorial
http://launchd.info/
Undocumented Mac OS X:第1回 initを置き換えるlaunchd【前編】 (1/3) - ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/0704/26/news009.html

2016年1月19日火曜日

SPF + DKIMを設定して、Postfixでメールを送信(CentOS)

Postfixを使ってメール送信できるように設定をする手順です。
素のPostfixでもメール送信はできますが、SPFやDKIMといったメールの信頼性を高める認証技術を利用してメールを送信できるようにします。

実は、Amazon SESにSPF + DKIMを設定してメール送信をしようとしていたのですが、Yahooメールにテストメールを送信したところ、迷惑メールに振り分けられていたりしてAmazon SESの使用を断念した経緯がありました。メールヘッダを見ると一部のAmazon SESメールサーバのIPアドレスがYahooのフィルタでブロックされている記述がありました。これでは、使いものにならないので、EC2上にMTAを構築してメール送信をすることにしました。[AWSイスターシリーズ] Amazon Simple Email Service の選択肢 1 です。

以下の例では、EC2にMTAとしてPostfixを利用してメール送信するにはどうしらよいか?調べたメモになります。
なお、メール受信については取り扱っていません。

環境は EC2 CentOS 6 (x86_64) - with Updates HVM です。
IPアドレスは 192.0.1.1、ドメインは example.com としています
ドメインの設定は AWS Route53 を使用しています。別のDNSサービスを使っているときは、サービスにフォーマットを合わせてください。
以下、設定手順です。

Postfix

IPV4のみを使うようにします。
# emacs /etc/postfix/main.cf
inet_protocols = ipv4

SPFレコード

AWS Route53 を利用してSPFレコードを設定します。
Create Record Setから2つのレコードを登録します。
Name: 入力なし
Type: TXT
Value: "v=spf1 +ip4:192.0.1.1 -all"
Name: 入力なし
Type: SPF
Value: "v=spf1 +ip4:192.0.1.1 -all"
digコマンドで設定を確認。
$ dig example.com txt
example.com. 48 IN TXT "v=spf1 +ip4:192.0.1.1 -all"

※ 既にSPFレコードを登録している場合。
1つのドメインに対して複数行のspf1レコードは公開できません。そのばあいは、以下のフォーマットで1行にまとめて登録します。
"v=spf1 ip4:192.0.1.1 ip4:192.0.2.2 include:_spf.google.com ~all"
※ 既に別のTXTレコードがある場合。
複数行併記すれば良いです。
"google-site-verification=xxxxxxxxxxxxxx"
"v=spf1 ip4:192.0.1.1 ip4:192.0.2.2 include:_spf.google.com ~all"

DKIM レコード

SELINUXを無効にする。
# getenforce
Enforcing
# setenforce 0
Permissive
# emacs /etc/selinux/config
SELINUX=enforcing
->
SELINUX=disabled

OpenDKIMをインストール。
# rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
# yum -y install opendkim
鍵ファイルの作成先ディレクトリを用意して、鍵を作成。 -s で指定するセレクタは自由に設定できますが、ここでは、dkimselector としました。
# mkdir /etc/opendkim/keys/example.com
# opendkim-genkey -D /etc/opendkim/keys/example.com -d example.com -s dkimselector
# chown opendkim:opendkim /etc/opendkim/keys/example.com/*
鍵の中を確認します。
# cat /etc/opendkim/keys/example.com/dkimselector.txt
dkimselector._domainkey IN TXT ( "v=DKIM1; k=rsa; "
   "p=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" )  ; ----- DKIM key dkimselector for example.com
Route 53に2つのレコードを登録します。 公開鍵レコードを登録。
Name: dkimselector._domainkey
Type: TXT
Value: "v=DKIM1; k=rsa; p=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
ADSPレコードを登録。
_adsp._domainkey.example.com.  IN TXT "dkim=unknown"
登録したレコードが正しく引けるかを確認します。
# dig dkimselector._domainkey.example.com txt
# dig _adsp._domainkey.example.com txt 
DNSの設定はこれで完了です。

OpenDKIMの設定をしていきます。設定ファイルを編集。
# cp /etc/opendkim.conf /etc/opendkim.conf.org
# emacs /etc/opendkim.conf
---
Mode    v
->
Mode    sv

KeyFile /etc/opendkim/keys/default.private
->
# KeyFile /etc/opendkim/keys/default.private

# KeyTable      /etc/opendkim/KeyTable
->
KeyTable        refile:/etc/opendkim/KeyTable

# SigningTable  refile:/etc/opendkim/SigningTable
->
SigningTable   refile:/etc/opendkim/SigningTable

# ExternalIgnoreList    refile:/etc/opendkim/TrustedHosts
->
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts

# InternalHosts refile:/etc/opendkim/TrustedHosts
->
InternalHosts  refile:/etc/opendkim/TrustedHosts
DKIM署名に使う秘密鍵を指定。
# emacs  /etc/opendkim/KeyTable
dkimselector._domainkey.example.com example.com:dkimselector:/etc/opendkim/keys/example.com/dkimselector.private
DKIM署名を行うドメイン名を指定。
# emacs  /etc/opendkim/SigningTable
*@example.com  dkimselector._domainkey.example.com
メール送信を行うPostfixサーバのIPアドレスを指定。デフォルトで127.0.0.1が記述してあるので、このままで良いです。
# emacs  /etc/opendkim/TrustedHosts
127.0.0.1
OpenDKIMを起動。
# service opendkim start
OpenDKIMの自動起動を設定しておきます。
# chkconfig opendkim on
PostfixからOpenDKIMを使えるように設定します。最終行に追加。
# emacs /etc/postfix/main.cf
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
Postfixをリロードします。
# service postfix reload
DKIM認証の設定が完了しました。

動作確認

Gmailなどへメールを送信してテストしてみます。
メールコマンドをインストールして、メール送信。
送り主を info@example.com、あて先を my-email-address@gmail.com としました。自身のメールアドレスに書き換えてください。
# yum -y install mailx
# echo "Test Mail." | mail -s "test mail" -r info@example.com my-email-address@gmail.com

メール送信ログ
DKIMフィールドが追加されている。
# tail -f /var/log/maillog
Jan 19 08:37:25 ip-192-0-1-1 opendkim[6565]: F182261A0A: DKIM-Signature field added (s=dkimselector, d=example.com)

メールヘッダ
下記のような表示があればOKです。
ヘッダーの Authentication-Results にGoogleが許可した認証を spf=pass, dkim=passのように追記しています。
Received-SPF: pass (google.com: domain of info@example.com designates 192.0.1.1 as permitted sender) client-ip=192.0.1.1;
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of info@example.com designates 192.0.1.1 as permitted sender) smtp.mailfrom=info@example.com;
       dkim=pass header.i=@example.com
DKIM-Filter: OpenDKIM Filter v2.10.3 ip-192-0-1-1CAD5961A13
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
 s=dkimselector; t=1453192772;
 bh=XXXXXX=;
 h=Date:From:To:Subject:From;
 b=XXXX/XXXX
  XXXX
  XXXX=

まとめ

SPFとDKIMを設定してメール送信できるようにしました。
これで、認証付きのメール送信は出来るようになりましたが、携帯キャリアなどに送信テストしていません。用途によって、ちゃんとメール送信・受信テストする必要があります。
他にも、メールサービスによっては、フィルタがきつくて受信できないところもあるかもしれません。また、大量メール配信はできないでしょう。
クリティカルな用途でなかったら、これで十分かもしれませんが。用途によっては、メールの到達率、可用性く、大量のワークロードを処理できる、などといった指標をクリアする商用のMTAを使うことをおすすめしたいです。

See Also.
間違いから学ぶSPFレコードの正しい書き方 : 迷惑メール対策委員会
http://salt.iajapan.org/wpmu/anti_spam/admin/operation/information/spf_i01/
複数の SPF レコード - Google Apps 管理者 ヘルプ
https://support.google.com/a/answer/4568483
Postfix DKIM 認証 設定メモ(CentOS6.5+OpenDKIM) | あぱーブログ
https://blog.apar.jp/linux/856/
SE奮闘記: PostfixにDKIMを設定して送信メールサーバーの信頼度を上げる
http://se-suganuma.blogspot.jp/2015/01/postfixdkim.html
Top photo from Bill Ward Mail Trucks