2015年12月21日月曜日

DockerでLet's EncryptのSSLを導入する

無料でSSL証明書を自動発行するサービス Let's Encrypt を利用して、nginxにSSL証明書を組み込む手順を紹介します。
通常、Let's Encryptの証明書を発行するには、Pythonやモジュールをインストールして、Lets's Encrypt Clientを使えるようにする必要がありますが、これらの環境構築を簡素化するためにDockerを利用します。
以下導入手順です。OSはCentOS 6を使用しています。

Dockerをインストール
# yum install docker-io
# service docker start
# service docker status
docker (pid  2257) is running...
※ もし。Dockerが起動できず。以下のようなメッセージが出るときは、カーネルのバージョンを上げる必要があります。
# cat /var/log/docker
\nWed Aug 17 15:27:36 JST 2016\n
time="2016-08-17T15:27:36.884624234+09:00" level=warning msg="You are running linux kernel version 2.6.32-431.17.1.el6.x86_64, which might be unstable running docker. Please upgrade your kernel to 3.10.0."
/usr/bin/docker: relocation error: /usr/bin/docker: symbol dm_task_get_info_with_deferred_remove, version Base not defined in file libdevmapper.so.1.02 with link time reference
yumで最新のカーネルをインストールして、サーバをリブートします。
# yum clean all
# yum -y upgrade

Let's Encryptで証明書を発行
コマンドを実行すると、--webroot-path で指定したパス以下に、.well-known/acme-challenge/* ディレクトリを作って認証ファイルが設置されます。
Let's Encryptの認証クローラが、このファイルにアクセスして認証処理をします。
Dockerコンテナから、外部ファイルへアクセスする必要があるので、 -v オプションに --webroot-path と同じパスを指定しておきます。

以下の例では、--webroot-path に /usr/share/nginx/html を指定。
同じく、-v "/usr/share/nginx/html":"/usr/share/nginx/html"を指定。
-d にドメイン historia.hitokoto.co を指定。
-m にメールアドレスを指定します。
# docker run -it --rm --name letsencrypt \
            -v "/etc/letsencrypt:/etc/letsencrypt" \
            -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
            -v "/usr/share/nginx/html":"/usr/share/nginx/html" \
            quay.io/letsencrypt/letsencrypt:latest certonly \
            --agree-tos \
            --webroot \
            --webroot-path /usr/share/nginx/html \
            -d historia.hitokoto.co \
            -m yako.takeshi@googlemail.com
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/historia.hitokoto.co/fullchain.pem. Your cert
   will expire on 2016-03-20. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
証明書ができた旨が示されます。
この例では、/etc/letsencrypt/live/historia.hitokoto.co/ 以下にファイルができています。 確認してみましょう。
# ls -al /etc/letsencrypt/live/historia.hitokoto.co
total 8
drwxr-xr-x 2 root root 4096 Dec 21 13:48 .
drwx------ 3 root root 4096 Dec 21 13:48 ..
lrwxrwxrwx 1 root root   44 Dec 21 13:48 cert.pem -> ../../archive/historia.hitokoto.co/cert1.pem
lrwxrwxrwx 1 root root   45 Dec 21 13:48 chain.pem -> ../../archive/historia.hitokoto.co/chain1.pem
lrwxrwxrwx 1 root root   49 Dec 21 13:48 fullchain.pem -> ../../archive/historia.hitokoto.co/fullchain1.pem
lrwxrwxrwx 1 root root   47 Dec 21 13:48 privkey.pem -> ../../archive/historia.hitokoto.co/privkey1.pem

OpenSSLのサーバコマンドで証明書をチェック
s_server を起動。オプションで証明書を指定します。さらに、-wwwオプションを付けて HTTPS サーバをエミュレートします。
# cd /etc/letsencrypt/live/historia.hitokoto.co
# openssl s_server -cert cert.pem -key privkey.pem -CAfile chain.pem -www
Using default temp DH parameters
Using default temp ECDH parameters
opensslのs_serverがポート 4433 番で接続待ちをしている状態になりました。

クライアントを起動。
$ openssl s_client
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X1
verify return:1
depth=0 CN = historia.hitokoto.co
verify return:1
---
Certificate chain
 0 s:/CN=historia.hitokoto.co
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X1
 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X1
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
以下略
ハンドシェイクの様子が流れて、入力待ちとなります。

Curlで確認してみます。
# curl https://historia.hitokoto.co:4433/ -H 'Host: historia.hitokoto.co' -v
* About to connect() to historia.hitokoto.co port 4433 (#0)
*   Trying 153.126.157.62... connected
* Connected to historia.hitokoto.co (153.126.157.62) port 4433 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using TLS_DHE_RSA_WITH_AES_256_CBC_SHA
* Server certificate:
*  subject: CN=historia.hitokoto.co
*  start date: Dec 21 03:49:00 2015 GMT
*  expire date: Mar 20 03:49:00 2016 GMT
*  common name: historia.hitokoto.co
*  issuer: CN=Let's Encrypt Authority X1,O=Let's Encrypt,C=US
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Accept: */*
> Host: historia.hitokoto.co
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 ok
< Content-type: text/html
<
以下略
SSLで通信ができていることが確認できました。

nginxにLet's Encryptの証明書を組み込む
DH鍵交換に使用するパラメータファイルを作成。
# openssl dhparam 2048 -out dhparam.pem
nginx設定ファイルを編集。serverディレクティブを以下のようにしました。
server {
listen 443;
server_name historia.hitokoto.co;
root /var/www/historia;
index index.php;
charset utf-8;
gzip on;
gzip_types text/css text/javascript application/json;
ssl on;
ssl_certificate /etc/letsencrypt/live/historia.hitokoto.co/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/historia.hitokoto.co/privkey.pem;
ssl_dhparam /etc/letsencrypt/live/historia.hitokoto.co/dhparam.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;';
# upload file size
client_max_body_size 20M;
## access log settings
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log ltsv;
## location /
location ~ \.php$ {
fastcgi_pass unix:/tmp/php.socket;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass_header "X-Accel-Redirect";
fastcgi_pass_header "X-Accel-Expires";
}
}
nginxをリスタート。
# service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:

Qualys SSL LabsでSSLの評価を試してみます。
https://www.ssllabs.com/ssltest/index.html

A+となりました。

SSL証明書を更新
証明書を発行する時とほぼ同じコマンドです。--renew-by-defaultオプションを付けています。
状況によって、--interactive=false --tty=false オプションも付けます。
# docker run --rm --name letsencrypt \
            -v "/etc/letsencrypt:/etc/letsencrypt" \
            -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
            -v "/usr/share/nginx/html":"/usr/share/nginx/html" \
            quay.io/letsencrypt/letsencrypt:latest certonly \
            --webroot \
            --webroot-path /usr/share/nginx/html \
            -d historia.hitokoto.co \
            --renew-by-default
証明書の期限が90日と短いです。そのため、cronなどでSSL証明書を自動更新するようにしておくと良いでしょう。

See Also.
Title image from Let's Encrypt - Overview
User Guide — Let's Encrypt 0.2.0.dev0 documentation
https://letsencrypt.readthedocs.org/en/latest/using.html#running-with-docker
Let's Encrypt を支える ACME プロトコル - Block Rockin’ Codes
http://jxck.hatenablog.com/entry/letsencrypt-acme
Linux - Let's Encrypt で証明書を取得した時の手順備忘録 - Qiita
http://qiita.com/TsutomuNakamura/items/4166423699061e38d296
letsencrypt - DockerでLet’s Encryptしよっか - Qiita
http://qiita.com/sawanoboly/items/9fdde1707de5e975dd15
Let's EncryptのSSL証明書で、Qualys SSLTestでA+評価を獲得するには - Qiita
http://qiita.com/dseg/items/bab80f6f14349fcd9c22
Dockerを導入する(CentOS版) - Qiita
http://qiita.com/zwirky/items/991f61a231f4e198a320

2015年12月20日日曜日

Android開発 AdMobを実装して、インタースティシャル広告を配信する

AdMobを使って、インタースティシャル広告を出してみます。いわゆる全画面広告です。
※AdMobの登録方法などは、前回の記事を参考にしてください。 -> Android開発 AdMobを実装して、バナー広告を配信する
今回は、バックボタンで画面を閉じるときにインタースティシャル広告を出すようにしました。
さらに、毎回、広告が出るとユーザとしてうざいのでは?と考えて、数回に1回だけ広告を出すようにしています。
以下、サンプルコードです。
Empty Activityで作りました。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.admob.com.admobinterstitialtest">
<!-- Include required permissions for Google Mobile Ads to run. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- This meta-data tag is required to use Google Play Services. -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Include the AdActivity configChanges and theme. -->
<activity
android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent" />
</application>
</manifest>
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "test.admob.com.admobinterstitialtest"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.google.android.gms:play-services-ads:8.3.0'
}
view raw build.gradle hosted with ❤ by GitHub
package test.admob.com.admobinterstitialtest;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.InterstitialAd;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private InterstitialAd interstitial;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create the interstitial.
interstitial = new InterstitialAd(this);
interstitial.setAdUnitId(getString(R.string.interstitial_ad_unit_id));
loadinterstitial();
// Listener for Ad
interstitial.setAdListener(new AdListener()
{
// When Closed Ad, Load new Ad
@Override
public void onAdClosed()
{
super.onAdClosed();
loadinterstitial();
}
});
}
private void loadinterstitial()
{
// Create ad request.
AdRequest adRequest = new AdRequest.Builder().build();
// Begin loading your interstitial.
interstitial.loadAd(adRequest);
}
// Show Interstitial Ad
private void showInterstitialAd()
{
// return, if Ad data is no loaded
if (!interstitial.isLoaded()) {
return;
}
// Show Ad
interstitial.show();
}
// Back button
@Override
public void onBackPressed()
{
super.onBackPressed();
// Make rand
Random rnd = new Random();
// Omikuji
int Omikuji = rnd.nextInt(2);
if (Omikuji == 0) {
// Go to Show Interstitial Ad
showInterstitialAd();
}
}
}
<resources>
<string name="app_name">AdMobInterstitialTEST</string>
<!-- Interstitial ad unit ID -->
<string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/1033173712</string>
</resources>
view raw strings.xml hosted with ❤ by GitHub

See Also.
インタースティシャル広告 | AdMob Android ガイド | Google Developers
https://developers.google.com/mobile-ads-sdk/docs/admob/android/interstitial?hl=ja

2015年12月19日土曜日

Android開発 AdMobを実装して、バナー広告を配信する

AdMobのバナー広告を表示する方法を書いていきます。

AdMobは、Googleが提供しているモバイルアプリ向けの広告サービスです。モバイルアプリ向けの広告サービスはいくつかの会社が提供しているのですが、もっとも導入が手軽なAdMobを扱ってみます。
AdMob以外にも、いろいろな広告会社のアプリ向け広告サービスがありますが、AdMobが他の広告会社より有利に立っている点といえば、AndroidとGoogleアカウントをひも付いていることによる、ターゲティング配信です。バナー広告といったスタンダードな枠では、ほかの追随を許さない収益性を得ることができる場合が多いです。
もし、アクティブユーザが多いアプリであるならば、ほかの会社のサービスを試してみても良いでしょう。AdMobは広告枠の種類が少ないので、より多くの広告のサイズや種類を選ぶことが出来るはずです。アプリのデザインにあった広告を出すことが大事だと思います。最近は、インタースティシャルの動画広告が人気出てるみたいですね。

さっそく実装してみましょう。
AdMobのアカウントを作っておきます。
https://apps.admob.com/

AdMobにアプリを登録して、広告ユニットIDをメモしておきます。
ca-app-pub-XXXXXXXXXXXXXXXX/NNNNNNNNNN のような文字列です。下の実装では、テスト用の広告ユニットIDを使用します。Google Playにアップロードするときに、自分の広告ユニットIDに書き換えておきましょう。

以下、AdMobバナー広告の実装方法です。
Empty Activityで作ります。
以下コードサンプルです。

build.gradle に compile 'com.google.android.gms:play-services:6.+' を追記。
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "test.admob.com.admobempty"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.google.android.gms:play-services:6.+'
}
view raw build.gradle hosted with ❤ by GitHub

AndroidManifest にいくつか追記。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.admob.com.admobempty">
<!-- Include required permissions for Google Mobile Ads to run-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--This meta-data tag is required to use Google Play Services.-->
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--Include the AdActivity configChanges and theme. -->
<activity android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent" />
</application>
</manifest>

strings.xml に広告ユニット ID を設定。テスト用の広告ユニットIDです。
<resources>
<string name="app_name">AdMob</string>
<string name="banner_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>
</resources>
view raw strings.xml hosted with ❤ by GitHub

レイアウトに AdView を配置。
広告に使用する追加の名前空間 http://schemas.android.com/apk/res-auto を追記、さらに、AdView 用の要素を追記。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:ads="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="test.admob.com.admobempty.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
ads:adSize="BANNER"
ads:adUnitId="@string/banner_ad_unit_id">
</com.google.android.gms.ads.AdView>
</RelativeLayout>

MainActivity クラスに広告を設置。
以下のクラスをインポートしておきます。
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
AdView を検索するコードを追加。AdRequest を作成して AdView に広告を読み込みます。
package test.admob.com.admobempty;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AdView mAdView = (AdView) findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
}
}
ビルドしてみましょう。
バナー広告が表示されたでしょうか?

See Also
Android クイック スタート | AdMob Android ガイド | Google Developers
https://developers.google.com/mobile-ads-sdk/docs/admob/android/quick-start?hl=ja

2015年12月8日火曜日

yum install php7 on CentOS


Let's isntall PHP 7 to CentOS by yum.

This is a CentOS 6.7.
# cat /etc/redhat-release
CentOS release 6.7 (Final)
Install remi repo, some librarys and PHP 7 with some extensions.
# rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
# yum install libxslt libxslt-devel postgresql-devel httpd-devel autoconf automake gd-devel
# yum install --disablerepo=base,updates --enablerepo=remi --enablerepo=remi-php70 php php-common php-mbstring php-pdo php-cli php-devel php-fpm php-mysqlnd php-gd php-bcmath php-pecl-geoip php-pgsql php-pecl-memcached php-pecl-uuid php-pear php-opcache
Show version of PHP. It's PHP 7.
# php -v
PHP 7.0.0 (cli) (built: Dec  5 2015 07:17:51) ( NTS )
Copyright (c) 1997-2015 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
Fine! That's it.

Top image from Ian Muttoo yum yum too

2015年12月4日金曜日

Go lang bbpd / Amazon DynamoDBのプロキシサーバ

Go Advent Calendar 2015 - Qiita 11/4の記事です。

Go言語で書かれた、Amazon DynamoDBのプロキシサーバの紹介をします。
もともと、他言語のPHPで書かれたウェブアプリケーションで、AWS SDK for PHPを利用してDynamoDBを使おうとしていたのですが、Class Preloader for PHPがうまく動かせず、全然パフォーマンスが出なくて困っていました。EC2 c4.xlarge nginx + php-fpm でベンチマークして800 req/s ほどでした。(PHP with Amazon DynamoDB
そのため、他言語でDynamoDBのプロキシサーバを立てて、そのAPIを経由してデータのSet / Getをしようと考えて、色々探しまわった結果、SmugMugという企業がオープンソースで公開している、Goで書かれた bbpd というソフトウェアを見つけました。これを試してみたところ、思いのほか簡単にパフォーマンスの良いプロキシサーバを建てることができたので、紹介したいと思います。同じように困っている方を想定して、Goをインストールするところから書いていきます。

環境: EC2 Instance Type: c4.xlarge / AMI ID: Amazon Linux AMI 2015.09.1 (HVM), SSD Volume Type - ami-383c1956

Goをインストール
$ sudo yum -y install golang zsh
$ go version
go version go1.4.2 linux/amd64
$ mkdir ~/gocode
$ echo 'export GOPATH=$HOME/gocode' >> ~/.bashrc
$ source $HOME/.bashrc

bbpdをインストール
$ go get github.com/smugmug/bbpd
$ sudo cp $GOPATH/bin/bbpd /usr/bin/
$ sudo cp $GOPATH/src/github.com/smugmug/bbpd/bin/bbpd/bbpd_ctl /usr/bin/
$ sudo cp $GOPATH/src/github.com/smugmug/bbpd/bin/bbpd/bbpd_daemon /usr/bin/
$ sudo chmod 755 /usr/bin/bbpd*
設定ファイルを編集。
アクセスキー、シークレットキーを入力。Tokyoリュージョンの場合は、us-east-1をap-northeast-1に変更。
$ cp $GOPATH/src/github.com/smugmug/godynamo/conf_file/test_aws-config.json ~/.aws-config.json
$ emacs ~/.aws-config.json
{
    "extends":[],
    "services": {
        "default_settings":{
            "params":{
                "access_key_id":"myAccessKey",
                "secret_access_key":"mySecret",
                "use_sys_log":true
            }
        },
        "dynamo_db": {
            "host":"dynamodb.us-east-1.amazonaws.com",
            "zone":"us-east-1",
            "scheme":"http",
            "port":80,
            "keepalive":false,
            "iam": {
                "use_iam":false,
                "role_provider":"",
                "access_key":"",
                "secret_key":"",
                "token":"",
                "base_dir":"",
                "watch":false
            }
        }
    }
}
bbpdデーモンをスタート。デフォルトでは、ポート番号12333と12334の2つを受け付けるようになります。
$ bbpd_ctl start
**** starting bbpd as daemon
bbpd - started
2015/11/10 07:40:12 global conf.Vals initialized
2015/11/10 07:40:12 not using iam, assume credentials hardcoded in conf file
2015/11/10 07:40:12 starting bbpd...
2015/11/10 07:40:12 induce panic with ctrl-c (kill -2 2888) or graceful termination with kill -[1,3,15] 2888
2015/11/10 07:40:12 trying to bind to port:12333
2015/11/10 07:40:12 init routing on port 12333
2015/11/10 07:40:12 global conf.Vals initialized
2015/11/10 07:40:12 not using iam, assume credentials hardcoded in conf file
2015/11/10 07:40:12 starting bbpd...
2015/11/10 07:40:12 induce panic with ctrl-c (kill -2 2894) or graceful termination with kill -[1,3,15] 2894
2015/11/10 07:40:12 trying to bind to port:12333
2015/11/10 07:40:12 port 12333 already in use
2015/11/10 07:40:12 trying to bind to port:12334
2015/11/10 07:40:12 init routing on port 12334
プロセスを確認。
$ pgrep -l bbpd
2888 bbpd
2894 bbpd
CurlでAPIを叩いてステータスを確認。
$ curl -H "X-Bbpd-Indent: true" "http://localhost:12333/Status"
{
 "Status": "ready",
 "AvailableHandlers": [
  "/DescribeTable/",
  "/DeleteItem",
  "/ListTables",
  "/CreateTable",
  "/UpdateTable",
  "/StatusTable/",
  "/PutItem",
  "/PutItemJSON",
  "/GetItem",
  "/GetItemJSON",
  "/BatchGetItem",
  "/BatchGetItemJSON",
  "/BatchWriteItem",
  "/BatchWriteItemJSON",
  "/UpdateItem",
  "/Query",
  "/Scan",
  "/RawPost/",
  "/"
 ],
 "Args": {
  "X-Bbpd-Indent": "set '-H \"X-Bbpd-Indent: True\" ' to indent the top-level json",
  "X-Bbpd-Verbose": "set '-H \"X-Bbpd-Verbose: True\" ' to get verbose output"
 },
 "Summary": {
  "StartTime": "2015-11-10 07:40:12.993368854 +0000 UTC",
  "RunningTime": "1m17.069634284s",
  "LongestResponse": "0.00ms",
  "AverageResponse": "0.00ms",
  "LastResponse": "no requests made yet",
  "ResponseCount": "0"
 }
}
bbpdが立ち上がりました。

テーブルを作成
$ curl -H "X-Amz-Target: DynamoDB_20120810.CreateTable" -X POST -d '
{
    "AttributeDefinitions": [
        {
            "AttributeName": "user_id", 
            "AttributeType": "S"
        }
    ], 
    "KeySchema": [
        {
            "AttributeName": "user_id", 
            "KeyType": "HASH"
        }
    ], 
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 100, 
        "WriteCapacityUnits": 100
    }, 
    "TableName": "bbpd-test"
}
' http://localhost:12333/
{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"user_id","AttributeType":"S"}],"CreationDateTime":1.447141682984E9,"ItemCount":0,"KeySchema":[{"AttributeName":"user_id","KeyType":"HASH"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":100,"WriteCapacityUnits":100},"TableArn":"arn:aws:dynamodb:ap-northeast-1:891959351900:table/bbpd-test","TableName":"bbpd-test","TableSizeBytes":0,"TableStatus":"CREATING"}}

Put
$ curl -H "X-Amz-Target: DynamoDB_20120810.PutItem"  -X POST -d '
{
    "Item": {
        "num": 1, 
        "numlist": [
            6, 
            7, 
            1, 
            2, 
            3, 
            9, 
            -7234234234.234234
        ], 
        "stringlist": [
            "pk1_a", 
            "pk1_b", 
            "pk1_c"
        ], 
        "user_id": "my_user_id1"
    }, 
    "TableName": "bbpd-test"
}
' http://localhost:12333/PutItemJSON

Get
$ curl -H "X-Amz-Target: DynamoDB_20120810.GetItem" -X POST -d '
{
    "Key": {
        "user_id": {
            "S": "my_user_id1"
        }
    }, 
    "TableName": "bbpd-test"
}
' "http://localhost:12333/GetItemJSON"
{"Item":{"num":1,"numlist":[6,3,9,7,2,1,-7.234234234234234e+09],"stringlist":["pk1_b","pk1_c","pk1_a"],"user_id":"my_user_id1"}
うまく動いていますね!

ベンチマーク
DynamoDB: Provisioned Read Capacity Units: 10000 / Provisioned Write Capacity Units: 10000
EC2: Instance type: c4.large, c4.xlarge, c4.2xlarge

Getのパフォーマンスを計測。
# emacs postdata.file
{
    "Key": {
        "user_id": {
            "S": "my_user_id1"
        }
    },
    "TableName": "bbpd-test"
}
# ab -n 100000 -c 100 -p postdata.file -T "application/json" http://localhost:12333/GetItemJSON

c4.large
Requests per second:    4754.25 [#/sec] (mean)

c4.xlarge
Requests per second:    8591.35 [#/sec] (mean)

c4.2xlarge
Requests per second:    8540.63 [#/sec] (mean) 
8000 req/s くらいあれば、殆どのウェブアプリケーションでは、要件を満たせるのではないでしょうか。

注意
更に、コネクション数を増やしていくと、DynamoDBのキャパシティを超えて、APIから400エラーが返ってきます。その場合は下記のコードでリトライ処理。
https://github.com/smugmug/godynamo/blob/master/authreq/authreq.go#L152
デフォルトでは、7回までリトライします。もし、"authreq.retryReq: failed retries on %s:%s"などといったログ出力されていなければ、リクエストが成功していますが。念のため、ログの出力をモニタリングしておくと良いでしょう。
ほかに、ファイルディスクリプタ上限も上げておくと良いです。

PHPからbbpdを使ってみる
PHPからbbpdを叩いたときのベンチマークです。phpのコードはこちら
Instance type: c4.xlarge
# ab -c 100 -n 10000 http://127.0.0.1/get_go.php
Requests per second:    4260.64 [#/sec] (mean)
AWS SDK for PHPを使った時は、800req/sくらいだったので、5倍パフォーマンスが良いです。これで、要件によっては、その範疇に収まるようにできるのではないでしょうか。選択肢のひとつとして考えてみるのもありかと思います。

GoのコードでGoDynamoを使ってみる
GoからGoDynamoを使うコードはこのような感じになります。プロダクション用に書くならば、テストコード(https://github.com/smugmug/godynamo/tree/master/tests)を参考にすると良いと思います。
package main
import (
"fmt"
"github.com/smugmug/godynamo/conf"
"github.com/smugmug/godynamo/conf_file"
get "github.com/smugmug/godynamo/endpoints/get_item"
"github.com/smugmug/godynamo/types/attributevalue"
"net/http"
)
func main() {
// Read GoDynamo AWS config file
conf_file.Read()
conf.Vals.ConfLock.RLock()
if conf.Vals.Initialized == false {
panic("the conf.Vals global conf struct has not been initialized")
}
conf.Vals.ConfLock.RUnlock()
// Get items
get1 := get.NewGetItem()
get1.TableName = "bbpd-test"
get1.Key["user_id"] = &attributevalue.AttributeValue{S: "my-user-id"}
body, code, err := get1.EndpointReq()
if err != nil || code != http.StatusOK {
fmt.Printf("get failed %d %v %s\n", code, err, body)
}
fmt.Printf("%v\n%v\n,%v\n", string(body), code, err)
}
view raw get.go hosted with ❤ by GitHub
package main
import (
"fmt"
"github.com/smugmug/godynamo/conf"
"github.com/smugmug/godynamo/conf_file"
put "github.com/smugmug/godynamo/endpoints/put_item"
"github.com/smugmug/godynamo/types/attributevalue"
"net/http"
"time"
)
func main() {
// Read GoDynamo AWS config file
conf_file.Read()
conf.Vals.ConfLock.RLock()
if conf.Vals.Initialized == false {
panic("the conf.Vals global conf struct has not been initialized")
}
conf.Vals.ConfLock.RUnlock()
// Make item data
put1 := put.NewPutItem()
put1.TableName = "bbpd-test"
// Set data with attributevalue
timestamp := fmt.Sprintf("%v", time.Now().Unix())
put1.Item["user_id"] = &attributevalue.AttributeValue{S: "my-user-id"}
put1.Item["timestamp"] = &attributevalue.AttributeValue{N: timestamp}
// Send the request
body, code, err := put1.EndpointReq()
if err != nil || code != http.StatusOK {
fmt.Printf("put1 failed %d %v %s\n", code, err, body)
}
fmt.Printf("%v\n%v\n,%v\n", string(body), code, err)
}
view raw put.go hosted with ❤ by GitHub

まとめ
bbpd・GoDynamoを使ってDynamoDBにアクセスしてみました。
GoDynamoの内部では、公式のAWS SDKと同じく、自動再試行ロジックを実装するなど、丁寧に作られている印象があって好感が持てました。とくに、bbpdに関しては、必要な機能だけを持たせたAPIサーバということで、マイクロサービス感がありました。今回は、PHPからbbpdを叩いてDynamoDBにアクセスするといった使い方をしていて、個人的にはAWS SDKのコードを修正したり、PHP ExtensionをCで頑張って書いたりするよりは楽だったのが良かったところです。

See Also.
SmugMug: From MySQL to Amazon DynamoDB (DAT204) | AWS re:Invent 2013 http://www.slideshare.net/AmazonWebServices/smugmug-from-mysql-to-amazon-dynamodb-dat204-aws-reinvent-2013
Handling Errors in DynamoDB Operations - Amazon DynamoDB https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html
GoDynamo
https://github.com/smugmug/godynamo
AWS SDK for Go - General Availability
https://aws.amazon.com/about-aws/whats-new/2015/11/aws-sdk-for-go-general-availability/
Amazon DynamoDB logo / Amazon Web Services LLC - http://aws.typepad.com/aws/2011/12/introducing-aws-simple-icons-for-your-architecture-diagrams.html
gopher-side_color.{ai,svg,png} was created by Takuya Ueda (http://u.hinoichi.net). Licensed under the Creative Commons 3.0 Attributions license. - https://github.com/golang-samples/gopher-vector#gopher-side_color
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)