27 Dec 2011

Postfix + Rails on Ubuntu

 Luckily or unluckily, I hadn't had to work with ActionMailer to send emails via Postfix till this time's setting hell. Because, applications I wrote to send emails from my gmail account was as-needed basis and, alternatively, I just could leave the process to send emails to Amazon SES.

 This time, an app I'm currently working on reached the maximum limit of number of emails sent by logging-in Google's SMTP, which was 1000 out-going mails a day with my account of Google Apps for free, and I adopted using SMTP server set up in a VPS. I looked for a summarised article on this but couldn't find nice one, really.

 I initially tried to get ActionMailer communicate with Qmail before Postfix. It did't work at all perhaps because it speaks slightly different SMTP apart from that of Sendmail and Postfix. So, I used combination of Postfix + Rails on Ubuntu and relayed mails to Gmail's SMTPs.

My Environment

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=10.04
DISTRIB_CODENAME=lucid
DISTRIB_DESCRIPTION="Ubuntu 10.04.3 LTS"

$ ruby --version
ruby 1.8.7 (2011-06-30 patchlevel 352) [i686-linux]

$ gem list action
*** LOCAL GEMS ***
actionmailer (3.0.0)

Installing Postfix

 There were two way to install binary version of Postfix with aptitude. One is to remove MTA currently installed, which forces you to install another MTA, and the other one is vice versa.

So, usual this one line is fine.
$ sudo aptitude install postfix


Setting up Postfix

 Rails ==plain SMTP==> Postfix ==SMTP with TLS==> Gmail's SMTP server

 Rails is able to communicates with Postfix without any unnecessary internal authentications in a box. Just set smtpd_use_tls=no in /etc/postfix/main.cf. It apparently forces other smtpd_tls_* options ineffective. I didn't get any difference when I commented out the other smtpd options.

 Make sure your setting of mynetworks thoroughly shuts out SMTP server/client from outside. You could check it by using telnet I will mention later.
 Let's reload to apply the setting.
$ sudo /etc/init.d/postfix reload

 Let's telnet to list your setting and check if TLS is not working.
$ telnet localhost 25
 :
EHLO localhost # => type this command
250-www24085u.sakura.ne.jp
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN # => make sure you haven't got a line '250-STARTTLS'

STARTTLS # => this command tells you if TLS is implemented
502 5.5.1 Error: command not implemented

You could use Telnet to check if your SMTP doesn't accept requests from outside
YourLocalBox$ telnet your.domain.com 25


 Now that your Postfix is ready to receive internal SMTP requests, let's move on to other options. It still doesn't send out emails because we haven't set up from who we send emails and to which SMTP server we relay emails.
Add the following line to specify your domain name in email address. For instance, set your.domain.com of notification@your.domain.com
$ sudo vim /etc/mailname
your.domain.com

$ sudo vim /etc/postfix/sasl/passwd
[smtp.gmail.com]:587    notification@your.domain.com:password # *0

$ sudo vim /etc/postfix/main.cf
myorigin = /etc/mailname
myhostname = www24085u.sakura.ne.jp # => *1

# Relay to Gmail
#relayhost = [smtp.gmail.com]:587 # => *2
smtp_use_tls = yes # => *3
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl/passwd
smtp_sasl_tls_security_options = noanonymous


*0. Make sure you set email address and password of your gmail account that you want to use as a sender. Also, note that Gmail's SMTP servers overwrite email address of a sender with this setting. It's MTS's right to do that.

*1. This is an option to set your host name in the case where you actually send email from another domain but set From in SMTP header email address you'd like to spoof (legitimately). I sent email with From header 'notification@emailsan.com' and Postfix was situated at www24085u.sakura.ne.jp. Thus, I set www24085u.sakura.ne.jp.

*2. I first set the option to relay mails but bumped a maximum limit of relaying for one SMTP server during load test.

*3. Postfix as a SMTP client need to use TLS to access Gmail's SMTP server. I'm not quite sure but SASL needs TLS to complete its functions


Reload or restart Postfix again.

Setting up Rails

In config/application.rb
# SMTP SETTING FOR local Postfix without any authorisation
ActionMailer::Base.smtp_settings = {
  :address              => "127.0.0.1",
  :port                 => 25, # => *1
  :domain               => 'your.hostname.com', # => *2
#  :user_name            => 'notification@your.domain.com,
#  :password             => 'password',
#  :authentication       => 'plain',
#  :tls   => true,
#  :openssl_verify_mode  => 'none',
  :enable_starttls_auto => false
}

*1. Don't need to use submission port, 587, because we just set Postfix to talk without any auth and encryption within a box.

*2. This must be the hostname where your rails app and Postfix is located

*. Do not add any of the lines commented out, which can cause ActionMailer's attempting SMTP Authentication. It looked like Actionmailer doesn't EHLO to determine either which protocol to talk nor which authentication method to use. "tail -f /var/log/mail.log" may give you line something like this if it's the case;
postfix/smtpd[4032]: lost connection after AUTH from your.hostname.com[ipaddress]
app/mailers/mailer_api.rb
# encoding: UTF-8

class MailerApi < ActionMailer::Base
  default :from => '"E mail-san" <notification@emailsan.com>', :parts_order  => ["text/plain", "text/html"] # => *1
    
  def compose to, subject, body_html, body_text
    @body_html = body_html
    @body_text = body_text
    mail :to => to, :subject => subject do |format|
      format.text # => *2
      format.html
    end
  end
  
end
*1. :parts_order specifies which part comes first. This matters when the mail you sent is bounced and, as a result, makes different which part to be shown in bounced email.

*2. It'd be better send multi-part mail if thinking of handling body of bounced mail, which is MIME-enveloped. Giving two line, format.text and format.html respectively, makes ActionMailer send multi parted mail.

To deliver an email;
MailerApi.compose(to, subject, body_html, body_text).deliver
 It's my assumption that my Postfix looks for which smtp to use with query to DNS and relay email to one of them. Befause I initially used 'relay=' option in main.cf and bumped limit. Then, comment out that line and grepping mail.log told me it still relays but various SMTP servers of Gmail.
Dec 28 01:50:12 www24085u postfix/smtp[11638]: 855E6A19A0: to=, orig_to=, relay=ASPMX.L.GOOGLE.com[74.125.53.26]:25, delay=10, delays=8.5/0.06/0.83/0.96, dsn=2.0.0, status=sent (250 2.0.0 OK 1325004612 l10si14011188pbj.164)

FYI, here's my MX record;
 MX  1 ASPMX.L.GOOGLE.COM. (TTL:300)
 MX  5 ALT1.ASPMX.L.GOOGLE.COM. (TTL:300)
 MX  5 ALT2.ASPMX.L.GOOGLE.COM. (TTL:300)
 MX  10 ASPMX2.GOOGLEMAIL.COM. (TTL:300)
 MX  10 ASPMX3.GOOGLEMAIL.COM. (TTL:300)




How to inspect

- Use telnet. It may tell you which step of communication is breaking down. e.g. Communication between Rails and Postfix or Postfix and Gmail's SMTP server. Trying SMTP Auth but failed. and so on..


- Set log level of main.cf the highest, 4; smtp_tls_loglevel=4 And tailing log files may give you details.


- Adding config.action_mailer.raise_delivery_errors = true to your development.rb or application.rb might give you useful information.

Verifying your domain

So, you can now send email with your Postfix. If you will send bulk of emails, you need to verify your domain in ideally two ways, which are SPF and DKIM, to prevent your account from being blocked by other MTAs.

SPF

Set SPF record into your DNS. In my case of application, I use 49.212.10.123 as a physical mail sender. And, send emails with it's From header 'notification@emailsan.com'. So, the record is ;
emailsan.com IN TXT  "v=spf1 ip4:49.212.10.123 include:aspmx.googlemail.com ~all"
You could nslookup if you want to;
$ nslookup -q=txt emailsan.com


You could check the results by clicking 'show original' button in just right adjacent the curving arrow in conversation pane in a new UI (as of Dec 2011) of Gmail. Below is side by side comparison of the before and after.

Before;
Authentication-Results: mx.google.com; spf=neutral (google.com: domain of notification@emailsan.com is neither permitted nor denied by best guess record for domain of example@cloudrop.jp)

After;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of notification@emailsan.com designates 49.212.10.123 as permitted sender) 


DKIM

The easiest way to set DKIM should using dkim-filter. I just followed introduction and got no trouble. PostfixDKIM And I got this in raw email in Gmail;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of notification@emailsan.com designates 49.212.10.123 as permitted sender) smtp.mail=notification@emailsan.com; dkim=pass (test mode) header.i=@emailsan.com
Note its seems that the majority using DKIM run it in test mode.


Hope this will save your time.

23 Dec 2011

Ruby programmerがApp Engineを使ったら

ここ最近App Engineを使って今の企画のバックエンドを担当していました。Ruby programmer として感じたことをまとめてみます。


どういう企画?

地図とメールを使ったインタラクティブアドのアプリです。詳しくは今のところ内緒になっています。

Python

 Python初めて書きました。今までPython=indentしてるからきっとだれにでも読みやすくなってる くらいの安易な認識しかなかった私。Rubyと一番違うと思ったのは必ず誰しもに必要とされるまで余分な実装しないというPythonの考え方。Rubyだと全てのクラスはObject型を継承してて、便利なirbでClass.public_methods.grep /name_of_method/ すればリファレンス見ることもなく大体使いたい関数が見つかる とか Enumerableのeachとかmap, reduce, injectみたいな”おかげでfor を全然書いてないし一行で大体配列もハッシュも加工できる”みたいな便利なものはなくてlambdaを使って自分で書いたりするほうが一般的ぽい といった基本ライブラリの考え方の違い。

 文法だと定数が無いとか(やりたかったらシングルトンでやらないといけない http://code.activestate.com/recipes/65207-constants-in-python/?in=user-97991)、switch 句が無いとか、Rubyは実行したファイル中でrequire, loadしていくと共有の名前空間のハイラルキーに追記していくけどPythonはファイルごとで都度 import, reloadした空間が有効になってるとか(多分一ヶ所でまとめてimportすればRubyと同じような動きを真似できると思う)とか 時々書いてて違いに気づかなくてアレ?てなったことはあった。文法戦争にあんまり興味いのでMatter of tasteだしMatter of type of app youre currently developingだと思う人なんですが、個人的には何でも揃ってるファジーさと直感的で自然言語で右脳的にドリブンして書けるRubyのあの感じが好きだからやっぱり簡単には他の言語に切り替えられない。ただ、RubyというよりはRails界隈の考え方は、少なくともver 2.2.xくらいまでは、運用時の論理というよりかはアプリの書き手の論理が先行する感じがあったのでPythonの足の軽さには考えさせられるものがありました。

Thread

 最初メールのバルクを配信するのに、エラー率をシングルトンで持っておいて各Threadで常にそれを監視するようなモデルに使用しようと思ってたけど、メールの一括配信はやらないことになったので一個のバルクがすごく小さいので結局使わなかったな。代わりに負荷テストで複数人が同時アクセスするのをエミュレートするのに使用した。以下ほぼApp Engineのサンプルコードから抜粋。最初、Thread proccess中で定義したerror_respsがどこを参照してるか分からなかった。しょうがないんで global error_respsとglobalにすることで参照できるようになったた。
error_resps = 0

def threadproc():
  """This function is executed by each thread."""
  h = httplib2.Http()

  while not quitevent.is_set():
    try:
      # HTTP requests to exercise the server go here
      # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      resp, content = h.request(random_url())
      if resp.status != 200:
        error_resps += 1 # Got an error something like undefined/uninitialised variable, error_resps
      # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

if __name__ == "__main__":
  try:
    for i in range(NUM_THREADS):
      # Create an instance of a thread with the method above
      t = Thread(target=threadproc)
      t.start()
      threads.append(t)


App Engineの機能

 使う前までApp EngineてVPSくらいに何でもセットアップして使うんだろうと思ってた(スゴイ)。だからなんでもpipのライブラリ使える分けじゃないと知ったときは残念だったけど、後でなんでもそろってると知って嬉しかった。大半のWeb開発で使うライブラリなんて大体決まってる。でも多分どうしても使いたいものはpackageして自分のプロジェクトに配置してそこからimportする方法がきっとあるんじゃないかな。一部socket通信を行うライブラリなどはセキュリティの理由から使用できないようだった。


webapps2

 今回フレームワークとしてwebapps2を採用しました。Djangoをよく知らないので比較できないけどSinatraみたいなRequest Handlerの書き方をします。一つのurlに対して一個のURLが割り当てられてget やpostを定義します。

 ディレクトリ構成はMVCパターンを採用しました。webappsはどのようにでも自由に組めるようになっていましたがConvention over configurationの世界に慣れているのでURLがAPPENGINE_ROOT/emailsだったらModelはEmail、ControllerはEmailsController、Viewはemailsというように自ら制約を設けて組みました。Modelはdatastoreのentity(RDBでいうところのtable)を扱い、ControllerはRequestを扱うHandlerです。Viewはjinja2というtemplate engineを使いました。


Task Queues

 重い処理を投げておくと後で実行しておいてくれるQueueです。メールを投げる処理がエラーを投げるだけじゃなくてメールドメインごとのエラー率を取るとか、一秒の送信数を調整できる様にしてたりとか、携帯には深夜送らないとか、エラー率上がってたらリトライとか その他いろいろやってるのでリクエストを返すのを早めるために、そういった処理もろもろを後でやっといてよとQueueにpushしています。Rubyの時はRedisを使ったresqueというRubygemを使っていたのが使い道が似てるかな

Scheduled Tasks

 一般的に言うところのCron jobです。cron.yamlに実行したいタスクを処理するurlを書くだけ。http://code.google.com/appengine/docs/python/config/cron.html urlはapp.yamlのroutingの定義でlogin: admin とすれば管理者とcronのprocessだけが実行できるようになる。なんでもweb appにしてしまうのはweb app感覚でそのままコーディングすればいいわけだからよく考えたらスマートだなあ。cronで loginじゃなくてsuだけしてると.bashrcが読まれてないとかchrootが何とかとかいろいろ気にしないでいいじゃん。

Backends

 変わって、今度はもっと重い処理をやってくれるBackendsです。Task queueもScheduled Tasksも30秒くらいで処理を終わらせないといけないとかいろいろ制限があってそれないは収まらない長く重い処理を担当してくれる。メール受信者一覧のCSVをあらかじめ生成するのに使おうと思っていたけれどCSV生成に16秒くらいしかかからなさそうだったので結局使ってない。スペックの良いBackends実行専用のinstanceが割り当てられるので、そこに処理を移譲すればいいとかいうふうになってたと思う。詳しくはWebで

Data store

 おはずかしながら今まで私はK/V storeをメインのDBとしては使ったことがなかった。今回も残念ながら日程がタイトだったため一番勉強しないといけないここが勉強できなかったのでなんだかよくわからないままになってしまった。テストの項でも書くけれど、AWSなんかでapplication serverをいくらload balancingしていてもRDBのreplicationがボトルネックになってスケールアウトしない、結局cachingをK/V storeにやらせるとか、そのへんがgoogleのbig tableを使ってのるとは違いがでてきてしまうんではないかなと思った。

Mail

 メール送信機能です。開発に使用中のライブラリの不具合は付き物です。もちろんメール送信は今回メインでしようする機能だったんですが送信メールのEnvelope FromがFrom宛になっておらずバウンスメールがロストするという不具合に遭遇してしまいメールのエラー率を取れなかった。そのため急遽自作でSMTPでログインしてsmtp.google.com経由でメール送信する外部(Rails)にRestful APIを作成した。ところがその外部APIに負荷テストをかけて一日何通送れるか試していたところSMTPで一日に送れる数というのが1000通までだけということが分かった。要件は一日5、6万通だった。

 以前 Amazon SESの1秒間や一日の送信制限どおりにrequestするライブラリを自分で書いていたのでそれを使ったら確実にメールを相手のinboxまで届けられるしSPF KDIMも一度やったから大丈夫と思ってた。ところが、初日に1000通、10日かけて1万通裁けるようになるとのこと。これでは要件をクリアできない。

 そこで今度は自分でSMTPを立てることにしたんだけれど、最初はqmail使ってみたくてやってみたらqmailと互換性がなくRailsのActionmailerからのSMTPプロトコルでの会話が全然成立してない。それで今度はPostfixにした。Postfixの認証をしないように設定していたんだけど、ActionmailerがEHLOして認証が必要ないのを確認していないみたいで/var/log/mail.log見るとどうもAUTHに失敗してるみたい。あーついてないとか思いながら睡眠不足を跳ね返してログをみながらRubyのコードを書き換えるという土臭い作業を繰り替えした。結局Actionmailer::Baseの設定に数あるうちのオプションのうち:passとか一定のオプションを与えると無条件で認証しようとするらしかったことが判明。どうにか自SMTPサーバでも負荷テストをやって1万7千件一時間で送れることを確認。今回これが一番苦労したかもしれないなー

うーん、あとで設定のノートでも書きたいなあ。

Quota

 その名も直訳して割り当て。App Engine利用者の利用制限です。APIの使用回数とか、Taskの容量とか..エトセトラなんだか予期せぬところにあるので一体どれくらいPV稼いだらいくらになるんだろうとかがわからなくて怖かった。Google側の資産利用コストがもちろん基準になっているとおもうんだけど、利用者の理論と乖離があるのでなんとかなったらなあと思うところ。だいたいどういう使い方したらいくらいくらですみたいなことを案内しているページとかがきっとありそう。VPS的に自分で上限をつけておけるパッケージとか、最初の超過は警告段階で課金されないとかあったらいいなあ。


Load Test

 これはすごかった。まだ何もプログラム書いてない段階でちゃんとスケールアウトするかなあとか心配してたんだけど、実際作り終わってみてテストしてみたらdatastoreへのqueryも何のそので、自分の書いたアプリがものすごく簡単だっただけに途中から自分のアプリを負荷試験しているというよりはApp Engineを試験してるみたいな申し訳ない気持ちでいっぱいになった。
QPSをこんなにあげてるのにResponseは0.6から0.7秒で推移した。通常時とほとんど変わらない。

まとめ

 そんなこんなで一プログラマがプログラミングだけほぼに集中できる環境、そしてGoogleの技術を借りてスケールできる環境が手に入ってしまうのはすごい。と今更ながら感動した。そしてまた端からGoogleのユーザを取り入れられるのも大きな利点。大量のデータを高速に扱うようなアプリ構築に向いていそう。