xyk blog

最近は iOS 開発の記事が多めです。

Git を理解するための濃い記事まとめ

後でまた見返したい Git を理解するための濃い記事まとめ。

www.slideshare.net

koseki.hatenablog.com

koseki.hatenablog.com

d.hatena.ne.jp

ja.astahblog.com

見えないチカラ: 【翻訳】Gitをボトムアップから理解する


チュートリアル

gitの入門用のチュートリアル"Learn Git Branching"を訳した | 48JIGEN *Reloaded*

k.swd.cc

iOS9対応メモ

環境: Xcode7 GM

iOS9 対応で行った作業メモ。

App Transport Security(ATS)対応

HTTPS 接続にする必要あり。今回はとりあえずHTTPを許可するようにInfo.plistに以下の手順で追加する。

  • NSAppTransportSecurityを Dictionary で追加。
  • その下でNSAllowsArbitraryLoadsをBooleanで追加し、YESを設定。

カスタムURLスキーム対応

openURLcanOpenURLのようなメソッドでカスタムURLスキームを呼び出す場合、Info.plistに登録しなければ使えないようになった。
admob SDK を使ってるのだが、アプリ起動時に以下のような許可されてないスキームのエラーが出るようになった。

 -canOpenURL: failed for URL: "itms-books://" - error: "This app is not allowed to query for scheme itms-books"
 -canOpenURL: failed for URL: "kindle://home" - error: "This app is not allowed to query for scheme kindle"

Info.plistに以下の手順で追加する。

  • LSApplicationQueriesSchemesを Array で追加。
  • その下で+を押すとitem 0が追加されるので String でitms-booksを入力。
  • さらに+を押してitem 1を追加し String でkindleを入力。

これで再度起動してみると、スキームが許可されてないエラーから、スキーム起動失敗のエラーに変わった。
これはスキームで起動させるアプリがインストールされていないためなので問題なし。

-canOpenURL: failed for URL: "itms-books://" - error: "(null)"
-canOpenURL: failed for URL: "kindle://home" - error: "(null)"

ここまでの作業でInfo.plistは以下のようになった。

f:id:xyk:20150915220436p:plain

Bitcode をオフ

実機でアプリを起動しようとしたら admob SDK で以下のエラーが出た。

ld: '/.../Google-Mobile-Ads-SDK/GoogleMobileAdsSdkiOS-7.4.1/GoogleMobileAds.framework/GoogleMobileAds(GADGestureIdUtil.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

とりあえず Bitcode をオフにすることでビルドが通るようになった。
Target -> Build Settings -> Build Options -> Enable Bitcode を NO に変更。

f:id:xyk:20150915220441p:plain

よく使う Xcode のショートカットメモ

developer.apple.com

Command(⌘) + Option(⌥) + [

選択行(複数行可)を上に移動

Command(⌘) + Option(⌥) + ]

選択行(複数行可)を上に移動

Command(⌘) + Control(^) + 左 or 右

超使う。ソースコード上の定義にジャンプしたり、戻ったり。
トラックパッドの左右スワイプでも同様なことができるがデカいファイル上で行うと固まったりするので、上のコマンドでやるほうがよい。

Command(⌘) + b

ビルド

Command(⌘) + r

ビルド&シミュレータ起動。

Command(⌘) + k

クリーン

Command(⌘) + .

ビルドのキャンセル。

Command(⌘) + 1

プロジェクトナビゲーター表示。左ペインのソースコードツリーのこと。
Command(⌘) + 2,3,4,5… で左ペインをメニューをそれぞれ表示する。

Command(⌘) + Shift(⇧) + j

現在、開いているファイルのプロジェクトナビゲーター上の位置に移動して表示。

Command(⌘) + f

現在開いているファイルに対して検索を行う。

Command(⌘) + Shift(⇧) + f

全ファイルに対して検索を行う。
さらに検索窓の上の 「Find」部分を「Replace」に入れ替えることで置換用窓が現れる。
これを使えば全ファイルを対象に一括置換が可能。置換前にプレビューで確認できるので安心して置換できる。

Command(⌘) + Shift(⇧) + o

ファイル名でインクリメンタル絞り込みを行いつつ検索する。

Command(⌘) + Option(⌥) + Enter / Option(⌥) + ファイルクリック

現在開いているウインドウを分割して2つのファイルを表示する。(画面分割形式 は Assistant Editor の設定で指定できる) ウィンドウを閉じるにはCmd + Enter。表示と違うので注意。

Command(⌘) + Option(⌥) + 0

Utilities の表示・非表示。画面右ペインのこと。
Command(⌘) + Option(⌥) + 2,3,4,5… で右ペインをメニューをそれぞれ表示する。

Command(⌘) + 0

画面左ペインの表示・非表示。

Command(⌘) + Shift(⇧) + <

Scheme 編集画面を開く。

Command(⌘) + Control(^) + [ or ]

Target を切り替える。


番外編

ブレイクポイントの削除

ブレイクポイントを枠外にドラッグで移動させて離すと削除される。
これ知らずに右クリックから Delete Breakpoint で削除してて面倒くさいと思ってた・・

Xcode の Localization でハマったのでメモ

環境:
Xcode 6.1
iOS 8.1 Simulator

結論から言うと Xcode 6.1 と iOS 8.1 Simulator の環境で、 iOS Simulator の言語設定に対応したローカライズファイルLocalizable.stringsを用意しても反映されないバグがあるらしい。

ワークアラウンドとして iOS Simulator 側の言語設定はなく、Xcode の Edit Scheme -> Run -> Option -> Application Language で言語設定すればいけた。

参考:

xcode - iOS 8.1 Simulator Localization broken (NSLocalizedString) - Stack Overflow
http://stackoverflow.com/questions/26504304/ios-8-1-simulator-localization-broken-nslocalizedstring
iOS8のシミュレーターでLocalizationのテストをする
http://www.rizastar.com/blog_xcode/ios8-simulator-localization-test/

ローカライズについてはこの資料が分かりやすかった。

Xcodeローカライズ処方箋 #yhios
http://www.slideshare.net/tomohirokumagai54/xcode-yhios

VPC 内に Elastic Beanstalk + RDS の環境構築して Rails アプリをデプロイする

環境:
Mac
aws-cli 1.7.0
eb-cli 3.0.10
Ruby 2.1.5
Rails 4.2.0

今回のポイント

  • VPC の環境構築(Subnet, IGW, Route Table)
  • VPC 内に Elastic Beanstalk 環境構築
  • Elastic Beanstalk のプラットフォームは Rubyを選択、Rails アプリをデプロイする。
    • Rails でブログアプリケーションを作る例とする。
  • RDS(MySQL) を作成してアプリと連携する。Beanstalk 作成時ではなく別途作成して関連付けしない。
  • ELB、EC2、RDS は VPC内のパブリックサブネットに置く。
    • RDS はプライベートサブネットに置き、接続するには NAT インスタンス経由にすべきだろうけど、今回はパブリックサブネットにおいてインターネットから直接接続できるようにした。
  • 無料枠で試すので EC2、RDS インスタンスは t2.micro を選択。
  • 環境構築はコマンドライン(aws-cli, eb-cli)だけで行う。
    • 管理コンソールからやったほうが簡単だけど1ヶ月もすると手順を忘れてしまうので、すべてコマンドで作成して、同じ環境を再現できるようにしたかった。

VPC からの環境構築は今回はじめてやるので理解が間違ってるところがあるかもしれない。

事前準備

IAM でユーザ作成し、「Administrator Access」権限を付与、AWS CLIaws configureコマンドでregionaws_access_key_idaws_secret_access_keyを設定しておくこと。

ざっくりやること一覧

  • VPC 作成
  • Subnet 作成
  • IGW 作成
  • Rroute Table 作成
  • DB パラメータグループ作成
  • DB サブネットグループ作成
  • VPC セキュリティグループ作成
  • RDS インスタンス作成
  • Rails アプリケーション作成
  • Elastic Beanstalk 環境設定
  • Elastic Beanstalk 環境作成
  • Rails アプリのデプロイ
  • 環境の削除

VPC 環境構築

VPC 作成

VPC を作成する。

$ aws ec2 create-vpc --cidr-block 10.0.0.0/16
{
    "Vpc": {
        "InstanceTenancy": "default",
        "State": "pending",
        "VpcId": "vpc-d42ae5b1",
        "CidrBlock": "10.0.0.0/16",
        "DhcpOptionsId": "dopt-9ed4cafc"
    }
}

VPC に Name タグを追加する。

$ aws ec2 create-tags --resources vpc-d42ae5b1 --tags Key=Name,Value="vpc blog"

VPC の作成と一緒に

も作成される。これらの関係性は以下の図のようになっている。

VPC のセキュリティ - Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Security.html

f:id:xyk:20150218160355p:plain

Route Table(main) を確認する。

$ aws ec2 describe-route-tables --filters "Name=vpc-id,Values=vpc-d42ae5b1"

...

            "RouteTableId": "rtb-f072a695",
            "VpcId": "vpc-d42ae5b1",

...

Route Table(main) に Name タグを追加する。

$ aws ec2 create-tags --resources rtb-f072a695 --tags Key=Name,Value="rtb blog main"

ACL を確認する。

$ aws ec2 describe-network-acls --filters "Name=vpc-id,Values=vpc-d42ae5b1"

...

            "NetworkAclId": "acl-2ace1b4f",
            "VpcId": "vpc-d42ae5b1",

...

ACL に Name タグを追加する。

$ aws ec2 create-tags --resources acl-2ace1b4f --tags Key=Name,Value="acl blog"

VPC セキュリティグループ(default)を確認する。

$ aws ec2 describe-security-groups --filters "Name=vpc-id,Values=vpc-d42ae5b1"

...

            "GroupName": "default",
            "VpcId": "vpc-d42ae5b1",
            "OwnerId": "123456789012",
            "GroupId": "sg-bdc574d8"

...

VPC セキュリティグループに Name タグを追加する。

$ aws ec2 create-tags --resources sg-bdc574d8 --tags Key=Name,Value="sg blog default"
VPCDNS ホスト名を有効にする

VPC 内の RDS に外部から接続できるようにするため。
参考: VPC の DNS サポートを更新する

# 有効にする
$ aws ec2 modify-vpc-attribute --vpc-id vpc-d42ae5b1 --enable-dns-hostnames

# 確認
$ aws ec2 describe-vpc-attribute --vpc-id vpc-d42ae5b1 --attribute enableDnsHostnames

{
    "VpcId": "vpc-d42ae5b1",
    "EnableDnsHostnames": {
        "Value": true
    }
}

Subnet 作成

3つのサブネットを作成する。
3つともパブリックサブネットとする。
2つ目、3つ目は DB 用として使うので availability-zone を分ける。
自分のアカウントではap-northeast-1aap-northeast-1cの2つが使用可能となっている。

$ aws ec2 create-subnet --vpc-id vpc-d42ae5b1 --cidr-block 10.0.0.0/24 --availability-zone ap-northeast-1a

{
    "Subnet": {
        "VpcId": "vpc-d42ae5b1",
        "CidrBlock": "10.0.0.0/24",
        "State": "pending",
        "AvailabilityZone": "ap-northeast-1a",
        "SubnetId": "subnet-f23be885",
        "AvailableIpAddressCount": 251
    }
}

$ aws ec2 create-subnet --vpc-id vpc-d42ae5b1 --cidr-block 10.0.1.0/24 --availability-zone ap-northeast-1a

{
    "Subnet": {
        "VpcId": "vpc-d42ae5b1",
        "CidrBlock": "10.0.1.0/24",
        "State": "pending",
        "AvailabilityZone": "ap-northeast-1a",
        "SubnetId": "subnet-ff3be888",
        "AvailableIpAddressCount": 251
    }
}

$ aws ec2 create-subnet --vpc-id vpc-d42ae5b1 --cidr-block 10.0.2.0/24 --availability-zone ap-northeast-1c

{
    "Subnet": {
        "VpcId": "vpc-d42ae5b1",
        "CidrBlock": "10.0.2.0/24",
        "State": "pending",
        "AvailabilityZone": "ap-northeast-1c",
        "SubnetId": "subnet-d721d58e",
        "AvailableIpAddressCount": 251
    }
}

サブネットに Name タグを追加する。

$ aws ec2 create-tags --resources subnet-f23be885 --tags Key=Name,Value="subnet public blog web"
$ aws ec2 create-tags --resources subnet-ff3be888 --tags Key=Name,Value="subnet public blog db1"
$ aws ec2 create-tags --resources subnet-d721d58e --tags Key=Name,Value="subnet public blog db2"

インターネットゲートウェイ作成

インターネットゲートウェイ(IGW)を作成する。

$ aws ec2 create-internet-gateway

{
    "InternetGateway": {
        "Tags": [],
        "InternetGatewayId": "igw-85d63ce0",
        "Attachments": []
    }
}

IGW に Name タグを追加する。

$ aws ec2 create-tags --resources igw-85d63ce0 --tags Key=Name,Value="igw blog"

VPC に IGW をアタッチする。

$ aws ec2 attach-internet-gateway --internet-gateway-id igw-85d63ce0 --vpc-id vpc-d42ae5b1

確認する。

$ aws ec2 describe-internet-gateways --internet-gateway-id igw-85d63ce0
{
    "InternetGateways": [
        {
            "Tags": [
                {
                    "Value": "igw blog",
                    "Key": "Name"
                }
            ],
            "InternetGatewayId": "igw-85d63ce0",
            "Attachments": [
                {
                    "State": "available",
                    "VpcId": "vpc-d42ae5b1"
                }
            ]
        }
    ]
}

パブリックサブネット用のルートテーブル作成

デフォルトでサブネットが紐付くメインルートテーブルとは別に新たにカスタムルートテーブルを作成する。
こちらをパブリックサブネット用のルートテーブルとして使用する。

$ aws ec2 create-route-table --vpc-id vpc-d42ae5b1

{
    "RouteTable": {
        "Associations": [],
        "RouteTableId": "rtb-2073a745",
        "VpcId": "vpc-d42ae5b1",
        "PropagatingVgws": [],
        "Tags": [],
        "Routes": [
            {
                "GatewayId": "local",
                "DestinationCidrBlock": "10.0.0.0/16",
                "State": "active",
                "Origin": "CreateRouteTable"
            }
        ]
    }
}

ルートテーブルに Name タグを追加する。

$ aws ec2 create-tags --resources rtb-2073a745 --tags Key=Name,Value="rtb blog public"

パブリックサブネット用ルートテーブルにインターネットゲートウェイを関連付ける

$ aws ec2 create-route --route-table-id rtb-2073a745 --destination-cidr-block 0.0.0.0/0 --gateway-id igw-85d63ce0

サブネットのルートテーブルを切り替え

作成した3つのサブネットに設定されているメインルートテーブルから、新たに作成したパブリックサブネット用ルートテーブルに切り替える。

$ aws ec2 associate-route-table --route-table-id rtb-2073a745 --subnet-id subnet-f23be885

{
    "AssociationId": "rtbassoc-70924015"
}

$ aws ec2 associate-route-table --route-table-id rtb-2073a745 --subnet-id subnet-ff3be888

{
    "AssociationId": "rtbassoc-73924016"
}

$ aws ec2 associate-route-table --route-table-id rtb-2073a745 --subnet-id subnet-d721d58e

{
    "AssociationId": "rtbassoc-7d924018"
}

VPC 環境構築はここまで。


RDS 環境構築

ここからは RDS の環境構築を行う。

以前の投稿も参考に。

  • MySQL 5.6(5.6.22) を使う。
  • Beanstalk 環境作成時に同時に RDS も作成できるが、Beanstalk に関連付けすると Beanstalk 環境の削除時に RDS も一緒に削除しようとするので別途作成する。
  • 今回は Multi-AZ Deployment はしない。

DB パラメーターグループ作成

DB パラメーターグループmydbparamgroupを作成する。

$ aws rds create-db-parameter-group \
--db-parameter-group-name mydbparamgroup \
--db-parameter-group-family mysql5.6 \
--description "for myinstance"

{
    "DBParameterGroup": {
        "DBParameterGroupName": "mydbparamgroup",
        "DBParameterGroupFamily": "mysql5.6",
        "Description": "for myinstance"
    }
}

DB パラメーターグループのキャラクタセット関連のパラメータを更新する。

$ aws rds modify-db-parameter-group --db-parameter-group-name mydbparamgroup --parameters \
ParameterName=character_set_client,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_connection,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_database,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_results,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_server,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=collation_connection,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate \
ParameterName=collation_server,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate \
ParameterName=skip-character-set-client-handshake,ParameterValue=0,ApplyMethod=pending-reboot

{
    "DBParameterGroupName": "mydbparamgroup"
}

続いて、init_connect パラメータを更新する。

$ aws rds modify-db-parameter-group --generate-cli-skeleton > init_connect.json

出力されたファイルを以下のように編集する。

{
    "DBParameterGroupName": "mydbparamgroup",
    "Parameters": [
        {
            "ParameterName": "init_connect",
            "ParameterValue": "SET SESSION time_zone = CASE WHEN POSITION('rds' IN CURRENT_USER()) = 1 THEN 'UTC' ELSE 'Asia/Tokyo' END;",
            "Description": "",
            "Source": "",
            "ApplyType": "",
            "DataType": "",
            "AllowedValues": "",
            "IsModifiable": true,
            "MinimumEngineVersion": "",
            "ApplyMethod": "immediate"
        }
    ]
}

そして DB パラメータグループを更新する。--cli-input-jsonオプションでファイルを指定。

$ aws rds modify-db-parameter-group --cli-input-json file://init_connect.json

# 終わったら不要なので削除
$ rm init_connect.json

DB サブネットグループを作成

DB サブネットグループmydbsubnetgroupを作成する。
DB 用に用意した2つのサブネットsubnet public blog db1subnet public blog db2を設定する。

$ aws rds create-db-subnet-group \
  --db-subnet-group-name mydbsubnetgroup \
  --db-subnet-group-description "DB SubnetGroup for myinstance" \
  --subnet-ids subnet-ff3be888 subnet-d721d58e

{
    "DBSubnetGroup": {
        "Subnets": [
            {
                "SubnetStatus": "Active",
                "SubnetIdentifier": "subnet-d721d58e",
                "SubnetAvailabilityZone": {
                    "Name": "ap-northeast-1c"
                }
            },
            {
                "SubnetStatus": "Active",
                "SubnetIdentifier": "subnet-ff3be888",
                "SubnetAvailabilityZone": {
                    "Name": "ap-northeast-1a"
                }
            }
        ],
        "DBSubnetGroupName": "mydbsubnetgroup",
        "VpcId": "vpc-d42ae5b1",
        "DBSubnetGroupDescription": "DB SubnetGroup for myinstance",
        "SubnetGroupStatus": "Complete"
    }
}

DB用 VPC セキュリティグループ作成

DB用 VPC セキュリティグループmyrdsを作成する。

ちなみに DB セキュリティグループと呼ばれるものがあるが、これは VPC 内ではない RDS に適用するもの。
昔の EC2-Classic Platform のアカウントのみ使える。

$ aws ec2 create-security-group \
    --group-name myrds \
    --description "RDS security group" \
    --vpc-id vpc-d42ae5b1

{
    "GroupId": "sg-14c77671"
}

DB用 VPC セキュリティグループに Name タグを追加する。

$ aws ec2 create-tags --resources sg-14c77671 --tags Key=Name,Value="sg blog rds"

RDS MySQL に外部から接続するために Inbound のルールを追加する。

$ aws ec2 authorize-security-group-ingress --group-id sg-14c77671 --protocol tcp --port 3306 --cidr 0.0.0.0/0

RDS インスタンス(MySQL)作成

RDS MySQL インスタンスを作成する。

  • db-instance-identifier はmyinstanceとする。
  • 上記で作成した DB パラメータグループをオプションで指定する。
  • セキュリティグループにデフォルト、上で作成したmyrdsの2つを追加する。
  • RDS MySQL に外部から接続したいので publicly-accessible を追加。
$ aws rds create-db-instance \
--db-instance-identifier myinstance \
--allocated-storage 5 \
--db-instance-class db.t2.micro \
--engine MySQL \
--engine-version 5.6.22 \
--master-username bloguser \
--master-user-password bloguser1234567890 \
--db-name blogdb \
--db-parameter-group-name mydbparamgroup \
--db-subnet-group-name mydbsubnetgroup \
--vpc-security-group-ids sg-bdc574d8 sg-14c77671 \
--storage-type standard \
--availability-zone ap-northeast-1a \
--no-multi-az \
--region ap-northeast-1 \
--publicly-accessible \
--no-auto-minor-version-upgrade

コマンドを実行すると RDS インスタンスの作成がはじまる。しばらく時間がかかるので待つ。
インスタンスの作成が完了したら、以下コマンドでエンドポイントを確認する。

$ aws rds describe-db-instances --db-instance-identifier myinstance | jq '.DBInstances[].Endpoint'
{
  "Port": 3306,
  "Address": "myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
}

PublicIp、PublicDnsName は以下コマンドで確認できる。
GUI 管理コンソールでは、EC2 Dashboard -> Network interfaces から確認できる。

$ aws ec2 describe-network-interfaces --filters "Name=description,Values=RDSNetworkInterface"

...
            "Description": "RDSNetworkInterface",
            "Association": {
                "PublicIp": "54.92.100.249",
                "PublicDnsName": "ec2-54-92-100-249.ap-northeast-1.compute.amazonaws.com",
                "IpOwnerId": "amazon-rds"
            },
...

上で調べた Endpoint または PublicIp、PublicDnsName のいずれかをホストに指定して MySQL に接続できるか確認する。

$ mysql -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p -D blogdb
$ mysql -h ec2-54-65-198-252.ap-northeast-1.compute.amazonaws.com -P 3306 -u bloguser -p -D blogdb
$ mysql -h 54.65.198.252 -P 3306 -u bloguser -p -D blogdb

RDS 環境構築はここまで。


Rails アプリケーション作成

Rails プロジェクト新規作成

サンプルとしてブログアプリケーションを作成する。
DB には MySQL を使う。ローカル環境に MySQL インストール済み。

$ bundle exec rails new blog -d mysql --skip-bundle
$ cd blog

# アプリケーションサーバとして Puma を選択するので Gemfile に追加
$ echo "gem 'puma'" >> Gemfile

# gem インストール
$ bundle install --path vendor/bundle
scaffold 実行

scaffold コマンドでアプリの雛形を作成する。

$ bin/rails generate scaffold article title:string content:string
ルート修正

config/routes.rb

Rails.application.routes.draw do
  resources :articles
  root 'articles#index'
end
タイムゾーンの設定

config/application.rb

    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
マイグレーション実行

ローカル環境の MySQL に対してマイグレーションを実行する。

$ bin/rake db:create && bin/rake db:migrate
動作確認

ローカル環境でアプリを起動して MySQL にデータ登録、画面にデータ表示できるか確認する。

$ bin/rails s
本番 DB 設定

本番用の DB 設定を修正する。

config/database.yml

production:
  adapter: mysql2
  encoding: utf8mb4
  database: <%= ENV['RDS_DB_NAME'] %>
  username: <%= ENV['RDS_USERNAME'] %>
  password: <%= ENV['RDS_PASSWORD'] %>
  host: <%= ENV['RDS_HOSTNAME'] %>
  port: <%= ENV['RDS_PORT'] %>
Git コミット

Git リポジトリを作成してソースコード一式を add & commit する。

$ git init && git add -A && git commit -m "first commit"

Rails アプリケーション作成まここまで。


Elastic Beanstalk 環境構築

ここからは Elastic Beanstalk の環境構築を行う。

以前の投稿も参考に。

先ほど作成した Rails プロジェクトのディレクトリ内で以下手順を行う。

アプリケーション枠の作成

eb initコマンドでアプリケーション枠を作成する。SSH キーペアは作成してアップロード済み。

$ eb init blog \
--platform "Ruby 2.1 (Puma)" \
--keyname aws-eb

.gitignore が更新されるので add & commit する。

$ git commit -am "updated .gitignore"

Elastic Beanstalk 環境のカスタマイズ設定

EC2 インスタンスタイムゾーンを日本に変更するため、カスタマイズ設定ファイルを準備する。

.ebextensionsディレクトリを作成する。

$ mkdir -p .ebextensions/scripts

Timezone を設定するシェルスクリプトを作成する。

.ebextensions/scripts/timezone.sh

#!/bin/bash
cp -f /usr/share/zoneinfo/Japan /etc/localtime
sed -i -e 's/ZONE=.*$/ZONE="Asia\/Tokyo"/' /etc/sysconfig/clock
sed -i -e 's/UTC=.*$/UTC=false/' /etc/sysconfig/clock
echo 'ARC=false' >> /etc/sysconfig/clock

.ebextensions ディレクトリ直下に config ファイルを作成する。

この config ファイルはデプロイする度に実行される。
タイムゾーン設定は初回の EC2 インスタンス作成時のみ実行すればよいので、test コマンドを使って2回目以降は実行されないようにした。

.ebextensions/01_timezone.config

container_commands:
  01-change_timezone:
    test: '[ ! -f /root/.not-a-new-instance.txt ]'
    command: bash .ebextensions/scripts/timezone.sh
  02-create_check_file:
    test: '[ ! -f /root/.not-a-new-instance.txt ]'
    command: touch /root/.not-a-new-instance.txt

ここまでできたら Git に add & commit する。

$ git add -A && git commit -m "add extensions"

Elastic Beanstalk 環境の作成

eb createコマンドでインスタンスの作成を行う。オプションについてはこちら

  • 環境名はblog-productionとする。
  • 作成したVPC(vpc blog)を指定する。
  • ELB と EC2 には同じパブリックサブネット(subnet public blog web)を指定する。
  • ELB と EC2 をパブリックサブネットに置くので、それぞれelbpublicpublicipを設定する。
  • デフォルトのセキュリティグループ(sg blog default)を指定する。
  • cname オプションでcom-example-blog-productionを設定する。
    • cname オプションを設定することで ELB に CNAME をつけることができる。URL スワップ時に使用する。
  • sample オプションをつける。

sample オプションをつける理由:
eb createコマンドを実行すると、環境構築 -> Rails アプリデプロイ -> DB マイグレーションまで行われるが、config/database.yml の DB 関連のパラメータは環境変数から取得するようなっており、まだ環境変数は未設定なのでパラメータが取得できず、DBマイグレーションに失敗してしまう。

しかし、eb setenvによる環境変数の設定はeb create実行後でなければできない。

ワークアラウンドとして、1発目のデプロイでは--sampleオプションを与えて、カレントディレクトリの Rails プロジェクトではなく、サンプルアプリケーションをデプロイさせる方法で回避する。

$ eb create blog-production \
--sample \
--cname com-example-blog-production \
--instance_type t2.micro \
--region ap-northeast-1 \
--tier webserver \
--vpc.ec2subnets subnet-f23be885 \
--vpc.elbsubnets subnet-f23be885 \
--vpc.id vpc-d42ae5b1 \
--vpc.securitygroups sg-bdc574d8 \
--vpc.publicip \
--vpc.elbpublic

Environment details for: blog-production
  Application name: blog
  Region: ap-northeast-1
  Deployed Version: None
  Environment ID: e-3qvcc2mas3
  Platform: 64bit Amazon Linux 2014.09 v1.2.0 running Ruby 2.1 (Puma)
  Tier: WebServer-Standard-1.0
  CNAME: com-example-blog-production.elasticbeanstalk.com
  Updated: 2015-02-18 08:19:01.355000+00:00
Printing Status:
INFO: createEnvironment is starting.
INFO: Using elasticbeanstalk-ap-northeast-1-123456789012 as Amazon S3 storage bucket for environment data.
INFO: Created security group named: sg-cac677af
INFO: Created load balancer named: awseb-e-3-AWSEBLoa-19XNPJOJ2L4UJ
INFO: Created security group named: sg-d2c677b7
INFO: Created Auto Scaling launch configuration named: awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingLaunchConfiguration-1JJZT8EC6TQTN
INFO: Waiting for EC2 instances to launch. This may take a few minutes.
INFO: Created Auto Scaling group named: awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:6c971967-dbd5-420c-b396-3e9077ca17c5:autoScalingGroupName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI:policyName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingScaleUpPolicy-1QMQK2YUBBKEQ
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:ec062391-358f-4296-af75-71b20f5d1677:autoScalingGroupName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI:policyName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingScaleDownPolicy-6YRVJB2IR2C2
INFO: Created CloudWatch alarm named: awseb-e-3qvcc2mas3-stack-AWSEBCloudwatchAlarmHigh-O9GN3OBFYQK3
INFO: Created CloudWatch alarm named: awseb-e-3qvcc2mas3-stack-AWSEBCloudwatchAlarmLow-18E1BGLOHWKS
INFO: Added EC2 instance 'i-f59d7eed' to Auto Scaling Group 'awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI'.
INFO: Application available at com-example-blog-production.elasticbeanstalk.com.
INFO: Successfully launched environment: blog-production

画面確認

$ eb open
# http://com-example-blog-production.elasticbeanstalk.com

Elastic Beanstalk 環境変数の設定

eb setenvコマンドで環境変数を設定する。

$ eb setenv \
SECRET_KEY_BASE=`bin/rake secret` \
RDS_DB_NAME=blogdb \
RDS_USERNAME=bloguser \
RDS_PASSWORD=bloguser1234567890 \
RDS_HOSTNAME=myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com \
RDS_PORT=3306

INFO: Environment update is starting.
INFO: Updating environment blog-production's configuration settings.
INFO: Successfully deployed new configuration to environment.
INFO: Environment update completed successfully.

確認

$ eb printenv
 Environment Variables:
     AWS_SECRET_KEY = None
     RDS_PORT = 3306
     RAILS_SKIP_ASSET_COMPILATION = false
     BUNDLE_WITHOUT = test:development
     RDS_PASSWORD = bloguser1234567890
     SECRET_KEY_BASE = 72ecc50883dd60d2668c5074053c5aae9c7acf5409eccab206053b2cf8e7039871d86ab349fa99cf208350ecbec5091dbd22a3f274b2cb242747d860f5fd6b93
     RACK_ENV = production
     PARAM5 = None
     PARAM4 = None
     PARAM3 = None
     PARAM2 = None
     PARAM1 = None
     RDS_USERNAME = bloguser
     RDS_DB_NAME = blogdb
     RAILS_SKIP_MIGRATIONS = false
     AWS_ACCESS_KEY_ID = None
     RDS_HOSTNAME = myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com

Rails アプリのデプロイ

ここでカレントディレクトリの Rails プロジェクトをデプロイする。

$ eb deploy
INFO: Environment update is starting.
INFO: Deploying new version to instance(s).
INFO: New application version was deployed to running EC2 instances.
INFO: Environment update completed successfully.

画面確認。

$ eb open

アプリ画面が表示されることを確認。
とりあえず適当なデータを1件登録してみる。

ここで Beanstalk で作成した EC2 インスタンスタイムゾーンと RDS に保存されたレコードの時間を確認する。

まず、EC2 インスタンスの PublicDns, PublicIp を確認する。

# 環境名 blog-production でフィルタする
$ aws ec2 describe-instances --filters "Name=tag-value,Values=blog-production" | jq '.Reservations[].Instances[] | {PublicDnsName,PublicIpAddress}'
# またはインスタンスID指定
# $ aws ec2 describe-instances --instance-ids i-f59d7eed --filters "Name=tag-value,Values=blog-production" | jq '.Reservations[].Instances[] | {PublicDnsName,PublicIpAddress}'

{
  "PublicDnsName": "ec2-54-65-4-37.ap-northeast-1.compute.amazonaws.com",
  "PublicIpAddress": "54.65.4.37"
}

EC2 インスタンスSSH ログインしてタイムゾーンを確認。

$ ssh -i ~/.ssh/aws-eb ec2-user@54.65.4.37

[ec2-user@ip-10-0-0-189 ~]$ date
2015年  2月 18日 水曜日 17:34:28 JST

RDS MySQL にコマンド接続して、セッションの現在時間、テーブルの登録データの日時を確認。

$ mysql -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p -D blogdb

mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2015-02-18 17:41:00 |
+---------------------+
1 row in set (0.01 sec)

mysql> select * from articles;
+----+-------+---------+---------------------+---------------------+
| id | title | content | created_at          | updated_at          |
+----+-------+---------+---------------------+---------------------+
|  1 | foo   | bar     | 2015-02-18 17:35:40 | 2015-02-18 17:35:40 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.01 sec)

mysql> \q

次回以降のデプロイでは DB マイグレーションが自動実行されないように環境変数RAILS_SKIP_MIGRATIONSを false から true に変更しておく。

$ eb setenv RAILS_SKIP_MIGRATIONS=true

ここまでで環境構築からアプリデプロイまでの工程が完了した。


後片付け

ここからは、ここまで作成してきたもの削除して元に戻す作業を行う。

Elastic Beanstalk リソース群の削除

ELB、EC2 などの Beanstalk リソース群のすべてを削除する。

$ eb terminate

The environment "blog-production" and all associated instances will be terminated.
To confirm, type the environment name: blog-production
INFO: terminateEnvironment is starting.
INFO: Deleted CloudWatch alarm named: awseb-e-3qvcc2mas3-stack-AWSEBCloudwatchAlarmLow-18E1BGLOHWKS
INFO: Deleted CloudWatch alarm named: awseb-e-3qvcc2mas3-stack-AWSEBCloudwatchAlarmHigh-O9GN3OBFYQK3
INFO: Deleted Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:ec062391-358f-4296-af75-71b20f5d1677:autoScalingGroupName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI:policyName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingScaleDownPolicy-6YRVJB2IR2C2
INFO: Deleted Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:6c971967-dbd5-420c-b396-3e9077ca17c5:autoScalingGroupName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI:policyName/awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingScaleUpPolicy-1QMQK2YUBBKEQ
INFO: Waiting for EC2 instances to terminate. This may take a few minutes.
INFO: Deleted Auto Scaling group named: awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingGroup-XQ9W5EU82FGI
INFO: Deleted Auto Scaling launch configuration named: awseb-e-3qvcc2mas3-stack-AWSEBAutoScalingLaunchConfiguration-1JJZT8EC6TQTN
INFO: Deleted load balancer named: awseb-e-3-AWSEBLoa-19XNPJOJ2L4UJ
INFO: Deleted security group named: sg-d2c677b7
INFO: Deleted security group named: sg-cac677af
INFO: Deleting SNS topic for environment blog-production.
INFO: terminateEnvironment completed successfully.

RDS 関連の削除

RDS インスタンスの削除

データも削除されるので注意!

# スナップショットは作らず削除
$ aws rds delete-db-instance --db-instance-identifier myinstance --skip-final-snapshot

# スナップショットを作ってから削除
$ aws rds delete-db-instance --db-instance-identifier myinstance \
--no-skip-final-snapshot \
--final-db-snapshot-identifier myinstance-final-snapshot

インスタンス削除が完了するまで待つ。

DB パラメーターグループの削除

DB パラメーターグループmydbparamgroupを削除する。

$ aws rds delete-db-parameter-group --db-parameter-group-name mydbparamgroup
DB サブネットグループの削除

DB サブネットグループmydbsubnetgroupを削除する。

$ aws rds delete-db-subnet-group --db-subnet-group-name mydbsubnetgroup
VPC セキュリティグループの削除

VPC セキュリティグループmyrdsを削除する。

$ aws ec2 delete-security-group --group-id sg-14c77671

VPC 関連の削除

Subnet 削除
$ aws ec2 delete-subnet --subnet-id subnet-f23be885
$ aws ec2 delete-subnet --subnet-id subnet-ff3be888
$ aws ec2 delete-subnet --subnet-id subnet-d721d58e
カスタム Route Table 削除
$ aws ec2 delete-route-table --route-table-id rtb-2073a745
IGW を VPC からデタッチする
$ aws ec2 detach-internet-gateway --internet-gateway-id igw-85d63ce0 --vpc-id vpc-d42ae5b1
IGW 削除
$ aws ec2 delete-internet-gateway --internet-gateway-id igw-85d63ce0
VPC 削除
$ aws ec2 delete-vpc --vpc-id vpc-d42ae5b1

同時に

  • Route Table(main)
  • Network ACL
  • Security Group(default)

も削除される。

ちなみにコマンドから削除する場合は、親の VPC から削除することはできず、上記のように子の方から順に依存関係を解除しないとその親は削除できない。
一方、GUIの管理コンソールから削除する場合は、親の VPC から削除することが可能で、その下に紐付くものすべて削除される(インスタンスが残っている場合などは無理)。

RDS(MySQL)の文字コードとタイムゾーンの設定

環境:
Mac
aws-cli 1.7.0
jq 1.4
RDS MySQL(5.6.22)

RDS MySQL文字コードタイムゾーンの設定を行ったのでその手順メモ。
操作はすべて aws-cliコマンドラインで行う。

1. 文字コードの設定

RDS MySQL文字コードおよび照合順序はデフォルトの状態でインスタンスを立ち上げると以下のようになる。

mysql> show global variables like 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | latin1 |
| character_set_connection | latin1 |
| character_set_database   | latin1 |
| character_set_filesystem | binary |
| character_set_results    | latin1 |
| character_set_server     | latin1 |
| character_set_system     | utf8   |
+--------------------------+--------+
7 rows in set (0.01 sec)

mysql> show global variables like 'collation%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
| collation_database   | latin1_swedish_ci |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+

登録するデータとして絵文字が含まれるので utf8mb4 を使えるように設定を行う。

DB パラメーターグループ作成

RDS の設定値を変更するにはDB パラメーターグループを通して行う。
まずは、mysql5.6 用の DB パラメーターグループの雛形を元にして新規の DB パラメーターグループをrds create-db-parameter-groupコマンドで作成する。

  • db-parameter-group-name はmydbparamgroupとする。
$ aws rds create-db-parameter-group --db-parameter-group-name mydbparamgroup --db-parameter-group-family mysql5.6 --description "for myinstance"

{
    "DBParameterGroup": {
        "DBParameterGroupName": "mydbparamgroup",
        "DBParameterGroupFamily": "mysql5.6",
        "Description": "for myinstance"
    }
}

DB パラメーターグループ更新

続いて作成した DB パラメーターグループのキャラクタセット関連のパラメータの初期値をrds describe-db-parametersコマンドで確認する。
ParameterValue はすべて未設定となっている。

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
 | jq '.Parameters[] | select(.ParameterName | contains("character") or contains("collation")) | { Description,DataType,Source,IsModifiable,ParameterName,ParameterValue,ApplyType }'

{
  "Description": "Don't ignore character set information sent by the client.",
  "DataType": "boolean",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character-set-client-handshake",
  "ParameterValue": null,
  "ApplyType": "static"
}
{
  "Description": "The character set for statements that arrive from the client.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_client",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The character set used for literals that do not have a character set introducer and for number-to-string conversion.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_connection",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The character set used by the default database.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_database",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The file system character set.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_filesystem",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The character set used for returning query results to the client.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_results",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The server's default character set.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character_set_server",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The collation of the connection character set.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "collation_connection",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "The server's default collation.",
  "DataType": "string",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "collation_server",
  "ParameterValue": null,
  "ApplyType": "dynamic"
}
{
  "Description": "Ignore character set information sent by the client.",
  "DataType": "boolean",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "skip-character-set-client-handshake",
  "ParameterValue": null,
  "ApplyType": "static"
}

DB パラメーターグループのキャラクタセット関連のパラメータをrds modify-db-parameter-groupコマンドで更新する。

  • character_set_filesystemcharacter_set_system以外のキャラクタセットutf8mb4を設定する。
  • collation はutf8mb4_general_ciにする。
  • skip-character-set-client-handshakeは TRUE にするとクライアント側が要求してきたキャラクタセットは無視して強制的にサーバ側のcharacter-set-serverで指定したキャラクタセットで応答するようになるらしい。
    今回はアプリのクライアント側でキャラクタセットを指定するのでこの設定はオフにする。
  • ApplyMethod は immediate は即時反映で、pending-reboot は再起動後の反映となる。
    Apply Type が dynamic のものは immediate で、static のものは pending-reboot となるようだ。
$ aws rds modify-db-parameter-group --db-parameter-group-name mydbparamgroup --parameters \
ParameterName=character_set_client,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_connection,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_database,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_results,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=character_set_server,ParameterValue=utf8mb4,ApplyMethod=immediate \
ParameterName=collation_connection,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate \
ParameterName=collation_server,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate \
ParameterName=skip-character-set-client-handshake,ParameterValue=0,ApplyMethod=pending-reboot

{
    "DBParameterGroupName": "mydbparamgroup"
}

DB パラメータグループに反映されたかrds describe-db-parametersコマンドで確認してみる。

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '[ .Parameters[] | select(.ParameterName | contains("character") or contains("collation")) | { key: .ParameterName, value: .ParameterValue } ] | from_entries'

{
  "character-set-client-handshake": null,
  "character_set_client": "utf8mb4",
  "character_set_connection": "utf8mb4",
  "character_set_database": "utf8mb4",
  "character_set_filesystem": null,
  "character_set_results": "utf8mb4",
  "character_set_server": "utf8mb4",
  "collation_connection": "utf8mb4_general_ci",
  "collation_server": "utf8mb4_general_ci",
  "skip-character-set-client-handshake": "0"
}

RDS MySQL インスタンス作成

create-db-instanceコマンドで RDS MySQL インスタンスを作成する。

  • 上記で作成した DB パラメータグループをオプションで指定する。
  • db-instance-identifier はmyinstanceとする。
  • RDS MySQL に外部から接続したいので publicly-accessible を追加。
$ aws rds create-db-instance \
--db-instance-identifier myinstance \
--allocated-storage 5 \
--db-instance-class db.t2.micro \
--engine MySQL \
--engine-version 5.6.22 \
--master-username bloguser \
--master-user-password bloguser1234567890 \
--db-name blogdb \
--db-parameter-group-name mydbparamgroup \
--storage-type standard \
--no-multi-az \
--region ap-northeast-1 \
--publicly-accessible \
--no-auto-minor-version-upgrade

コマンドを実行すると RDS インスタンスの作成がはじまる。しばらく時間がかかるので待つ。

  • VPC 指定をしていないので RDS インスタンスdefault VPC内に作成される。
  • セキュリティグループはdefault VPC security groupが設定される。
  • DBサブネットグループはdefaultが設定される。

インスタンスの作成が完了したら、rds describe-db-instancesコマンドでエンドポイントを確認する。

$ aws rds describe-db-instances --db-instance-identifier myinstance

...
            "Endpoint": {
                "Port": 3306,
                "Address": "myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
            },
...

PublicIp、PublicDnsName はec2 describe-network-interfacesコマンドで確認できる。
GUI 管理コンソールでは、EC2 Dashboard -> Network interfaces から確認できる。

$ aws ec2 describe-network-interfaces --filters "Name=description,Values=RDSNetworkInterface"

...
            "Description": "RDSNetworkInterface",
            "Association": {
                "PublicIp": "54.92.100.249",
                "PublicDnsName": "ec2-54-92-100-249.ap-northeast-1.compute.amazonaws.com",
                "IpOwnerId": "amazon-rds"
            },
...

デフォルトセキュリティグループに Inbound ルール追加

RDS MySQL に外部から接続するためにdefault VPC security groupに Inbound のルールを追加する。
セキュリティグループ ID を確認。

$ aws rds describe-db-instances --db-instance-identifier myinstance

...
        "VpcSecurityGroups": [
            {
                "Status": "active",
                "VpcSecurityGroupId": "sg-xxxxxxxx"
            }
        ],
...

ec2 authorize-security-group-ingressコマンドで追加する。

$ aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 3306 --cidr 0.0.0.0/0

RDS MySQL のキャラクタセット確認

上で調べた Endpoint または PublicIp、PublicDnsName のいずれかをホストに指定して MySQL に接続する。

$ mysql -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p -D blogdb

MySQL に接続できたら、キャラクタセットの設定を確認する。

mysql> show create database blogdb\G
*************************** 1. row ***************************
       Database: blogdb
Create Database: CREATE DATABASE `blogdb` /*!40100 DEFAULT CHARACTER SET utf8mb4 */
1 row in set (0.01 sec)

mysql> show variables like 'character\_set\_%';
+--------------------------+---------+
| Variable_name            | Value   |
+--------------------------+---------+
| character_set_client     | utf8    |
| character_set_connection | utf8    |
| character_set_database   | utf8mb4 |
| character_set_filesystem | binary  |
| character_set_results    | utf8    |
| character_set_server     | utf8mb4 |
| character_set_system     | utf8    |
+--------------------------+---------+

mysql> show global variables like 'character\_set\_%';
+--------------------------+---------+
| Variable_name            | Value   |
+--------------------------+---------+
| character_set_client     | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database   | utf8mb4 |
| character_set_filesystem | binary  |
| character_set_results    | utf8mb4 |
| character_set_server     | utf8mb4 |
| character_set_system     | utf8    |
+--------------------------+---------+

mysql> show variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8_general_ci    |
| collation_database   | utf8mb4_general_ci |
| collation_server     | utf8mb4_general_ci |
+----------------------+--------------------+

mysql> show global variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_general_ci |
| collation_server     | utf8mb4_general_ci |
+----------------------+--------------------+

global variables は指定通り utf8mb4 になっているが、session の variables は utf8 になっている箇所がある・・・

MySQL クライアント側のキャラクタセットがどうなっているか調べてみる。

今、手元の Mac にインストールした MySQL(Server version: 5.6.19 Homebrew)から接続しているので、そのローカルの MySQL にログインする。

$ mysql -uroot

mysql> show variables like 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | utf8   |
| character_set_connection | utf8   |
| character_set_database   | utf8   |
| character_set_filesystem | binary |
| character_set_results    | utf8   |
| character_set_server     | utf8   |
| character_set_system     | utf8   |
+--------------------------+--------+

mysql> show global variables like 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | utf8   |
| character_set_connection | utf8   |
| character_set_database   | utf8   |
| character_set_filesystem | binary |
| character_set_results    | utf8   |
| character_set_server     | utf8   |
| character_set_system     | utf8   |
+--------------------------+--------+

mysql> show variables like 'collation%';
+----------------------+-----------------+
| Variable_name        | Value           |
+----------------------+-----------------+
| collation_connection | utf8_general_ci |
| collation_database   | utf8_general_ci |
| collation_server     | utf8_general_ci |
+----------------------+-----------------+

mysql> show global variables like 'collation%';
+----------------------+-----------------+
| Variable_name        | Value           |
+----------------------+-----------------+
| collation_connection | utf8_general_ci |
| collation_database   | utf8_general_ci |
| collation_server     | utf8_general_ci |
+----------------------+-----------------+

character_set_clientなど、すべて utf8 になっていたのでこれが原因だろう。クライアント側でキャラクタセットを指定して再度、RDS MySQL に接続してみる。

コマンドラインのオプションで--default-character-setを指定する。

$ mysql --default-character-set=utf8mb4 -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p

再度、キャラクタセットの設定を確認する。

mysql> show variables like 'character\_set\_%';
+--------------------------+---------+
| Variable_name            | Value   |
+--------------------------+---------+
| character_set_client     | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database   | utf8mb4 |
| character_set_filesystem | binary  |
| character_set_results    | utf8mb4 |
| character_set_server     | utf8mb4 |
| character_set_system     | utf8    |
+--------------------------+---------+

mysql> show global variables like 'character\_set\_%';
+--------------------------+---------+
| Variable_name            | Value   |
+--------------------------+---------+
| character_set_client     | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database   | utf8mb4 |
| character_set_filesystem | binary  |
| character_set_results    | utf8mb4 |
| character_set_server     | utf8mb4 |
| character_set_system     | utf8    |
+--------------------------+---------+

mysql> show variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_general_ci |
| collation_server     | utf8mb4_general_ci |
+----------------------+--------------------+

mysql> show global variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_general_ci |
| collation_server     | utf8mb4_general_ci |
+----------------------+--------------------+

今度はセッションの設定も utf8mb4 となった。

一応、skip-character-set-client-handshakeを TRUE の場合もやってみた。
DB パラメータグループを更新する。

$ aws rds modify-db-parameter-group --db-parameter-group-name mydbparamgroup --parameters \
ParameterName=skip-character-set-client-handshake,ParameterValue=1,ApplyMethod=pending-reboot

変更を反映させるためにrds reboot-db-instanceコマンドで RDS インスタンスの再起動させる。

$ aws rds reboot-db-instance --db-instance-identifier myinstance

同じように RDS MySQL に接続してキャラクタセットを確認したところ、クライアント側で要求したキャラクタセットは無視されて、すべて utf8mb4 となることが確認できた。


2. タイムゾーンの設定

※追記:タイムゾーンの設定が可能になり以下で設定できる。init_connectを使ったワークアラウンドは不要になった

$ aws rds modify-db-parameter-group --db-parameter-group-name mydbparamgroup --parameters \
ParameterName=time_zone,ParameterValue=Asia/Tokyo,ApplyMethod=immediate

Amazon Web Services ブログ: Amazon RDS (MySQL, MariaDB)がlocal time zoneをサポートしました
http://aws.typepad.com/aws_japan/2015/12/amazon-rds-local-timezone-support.html

--
以下、タイムゾーンの変更ができなかった頃のワークアラウンド

RDS MySQLタイムゾーンJST にしたいが、現時点ではタイムゾーンの変更はできず、デフォルトの UTC となっている。
調べてみるとワークアラウンドとして、init_connect変数を使って接続毎にタイムゾーンを設定する方法があるようなので、それをやってみる。
以下を参考にさせてもらった。

AWS - RDS(MySQL)でJSTを使う たった1つの冴えたやり方 - Qiita
http://qiita.com/j3tm0t0/items/089ef96ba131df079ca4
MySQL,RDS - RDS(MySQLエンジンにてタイムゾーンを変更する方法を整理) - Qiita
http://qiita.com/mitzi2funk/items/4726986e8288b1599786
» AWS RDSのタイムゾーンについて Tech Fun.cc
http://techfun.cc/aws/aws-rds-timezone.html

init_connectで単純にSET SESSION time_zone = 'Asia/Tokyo';とやるのでは問題が発生するとのこと。
RDS 管理用のrdsadminというユーザがいて、このユーザが何かする時に Timezone が UTC 以外だと不具合が発生してしまうみたい。
以下のようにrdsadminユーザ以外の接続時のみ Timezone を変更するようにすればよい。

SET SESSION time_zone = CASE WHEN POSITION('rds' IN CURRENT_USER()) = 1 THEN 'UTC' ELSE 'Asia/Tokyo' END;

デフォルトの設定を確認する

RDS MySQL にログインしてタイムゾーンを確認する。

# ローカル Mac の現在時間
$ date
2015年 2月17日 火曜日 01時27分19秒 JST


# RDS MySQL に接続
$ mysql -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p


# 現在時間、タイムゾーン確認
mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2015-02-16 16:27:51 |
+---------------------+

mysql> show variables like '%time_zone';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| system_time_zone | UTC   |
| time_zone        | UTC   |
+------------------+-------+

mysql> show global variables like '%time_zone';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| system_time_zone | UTC   |
| time_zone        | UTC   |
+------------------+-------+

UTC となっている。ローカルは JST なので当然ながら時間はズレている。

init_connect変数を設定

DB パラメータグループのinit_connect変数を更新する。
SET 文を aws-cli の直接コマンドに埋め込むのではなく、ファイルに書いてそれをアップロードする方法で行う。 まずはテンプレートとなるファイルを--generate-cli-skeletonオプションで出力させる。

$ aws rds modify-db-parameter-group --generate-cli-skeleton > init_connect.json

出力されたファイルを以下のように編集する。

{
    "DBParameterGroupName": "mydbparamgroup",
    "Parameters": [
        {
            "ParameterName": "init_connect",
            "ParameterValue": "SET SESSION time_zone = CASE WHEN POSITION('rds' IN CURRENT_USER()) = 1 THEN 'UTC' ELSE 'Asia/Tokyo' END;",
            "Description": "",
            "Source": "",
            "ApplyType": "",
            "DataType": "",
            "AllowedValues": "",
            "IsModifiable": true,
            "MinimumEngineVersion": "",
            "ApplyMethod": "immediate"
        }
    ]
}

そしてDB パラメータグループを更新する。--cli-input-jsonオプションでファイルを指定。

$ aws rds modify-db-parameter-group --cli-input-json file://init_connect.json

# 終わったら不要なので削除
$ rm init_connect.json

設定が反映されるまで待つ。
反映されたら再度 RDS MySQL に接続して確認してみる。

# ローカル Mac の現在時間
$ date
2015年 2月17日 火曜日 01時38分03秒 JST


# RDS MySQL 接続
$ mysql -h myinstance.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u bloguser -p


# 現在時間、タイムゾーン確認
mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2015-02-17 01:38:18 |
+---------------------+

mysql> show variables like '%time_zone';
+------------------+------------+
| Variable_name    | Value      |
+------------------+------------+
| system_time_zone | UTC        |
| time_zone        | Asia/Tokyo |
+------------------+------------+

mysql> show global variables like '%time_zone';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| system_time_zone | UTC   |
| time_zone        | UTC   |
+------------------+-------+

このセッションのtime_zoneAsia/Tokyoになっているのが確認できた。

後片付け

default VPC security groupに追加した Inbound ルールをec2 revoke-security-group-ingressコマンドで取り消す。

$ aws ec2 revoke-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 3306 --cidr 0.0.0.0/0

RDS インスタンスmyinstancerds delete-db-instanceコマンドで削除する(データも消えるので注意!)。

# スナップショットは作らず削除
$ aws rds delete-db-instance --db-instance-identifier myinstance --skip-final-snapshot

# スナップショットを作ってから削除
$ aws rds delete-db-instance --db-instance-identifier myinstance \
--no-skip-final-snapshot \
--final-db-snapshot-identifier myinstance-final-snapshot

インスタンス削除が完了するまで待つ。

DB パラメーターグループmydbparamgrouprds delete-db-parameter-groupコマンドで削除する。

$ aws rds delete-db-parameter-group --db-parameter-group-name mydbparamgroup

以上。


補足

DB パラメータグループの JSON データから jq コマンドで目的のデータを抽出するためにいろいろ試行錯誤しながら試した。
記録しておかないと次に再現できないのでメモしておく。

JSON データ

{
    "Parameters": [
        {
            "Description": "Controls whether user-defined functions that have only an xxx symbol for the main function can be loaded",
            "DataType": "boolean",
            "AllowedValues": "0,1",
            "Source": "engine-default",
            "IsModifiable": false,
            "ParameterName": "allow-suspicious-udfs",
            "ApplyType": "static"
        },
        {
            "Description": "The MySQL installation base directory.",
            "DataType": "string",
            "IsModifiable": false,
            "Source": "system",
            "ParameterValue": "/rdsdbbin/mysql",
            "ParameterName": "basedir",
            "ApplyType": "static"
        },

        ...
    ]
}

上記の元データから jq コマンドを使って、あるパラメータに特定の文字列を含むものだけを抽出して出力してみる。
jq コマンドはjq '.'jq '.Parameters[]'のようにシングルクォートで囲ったフィルタ式を渡すことで実行する。

例1

ParameterName に"character"を含むものを抽出する。

  1. .Parameters[]で Parameters の配列の全ての要素を取り出して出力する。.Parametersとは違う。
  2. contains()で()内の入力値に部分一致するかどうかの真偽値を返す。
  3. select()で ()内の条件が真になれば、その要素を出力する、偽であれば何も返さない。
$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(.ParameterName | contains("character"))'

下は上と同じように動作する、別の書き方。

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(contains({ParameterName: "character"}))'

出力結果:

{
  "Description": "Don't ignore character set information sent by the client.",
  "DataType": "boolean",
  "AllowedValues": "0,1",
  "Source": "engine-default",
  "IsModifiable": true,
  "ParameterName": "character-set-client-handshake",
  "ApplyType": "static"
}
{
  "Description": "The character set for statements that arrive from the client.",
  "DataType": "string",
  "IsModifiable": true,
  "AllowedValues": "big5,dec8,cp850,hp8,koi8r,latin1,latin2,swe7,ascii,ujis,sjis,hebrew,tis620,euckr,koi8u,gb2312,greek,cp1250,gbk,latin5,armscii8,utf8,cp866,keybcs2,macce,macroman,cp852,latin7,utf8mb4,cp1251,cp1256,cp1257,binary,geostd8,cp932,eucjpms",
  "Source": "user",
  "ParameterValue": "utf8mb4",
  "ParameterName": "character_set_client",
  "ApplyType": "dynamic"
}
...
例2

{}を利用することで、JSON の内容を組み替えたオブジェクトを生成して出力してる。

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(.ParameterName | contains("character")) | { desc: .Description, name: .ParameterName, value: .ParameterValue }'

出力結果:

{
  "desc": "Don't ignore character set information sent by the client.",
  "name": "character-set-client-handshake",
  "value": null
}
{
  "desc": "The character set for statements that arrive from the client.",
  "name": "character_set_client",
  "value": "utf8mb4"
}
...
例3
  1. "character"を含むものを抽出
  2. ParameterName, ParameterValue を key, value という名前のプロパティに変更してオブジェクトを作成
  3. []で囲んで配列を作成
  4. from_entriesで key-value のペアからオブジェクトへ変換
$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '[ .Parameters[] | select(.ParameterName | contains("character")) | { key: .ParameterName, value: .ParameterValue} ] | from_entries'

出力結果:

{
  "character-set-client-handshake": null,
  "character_set_client": "utf8mb4",
  "character_set_connection": "utf8mb4",
  "character_set_database": "utf8mb4",
  "character_set_filesystem": null,
  "character_set_results": "utf8mb4",
  "character_set_server": "utf8mb4",
  "skip-character-set-client-handshake": "0"
}
例4

"character"または、"collation"を含むものを抽出。orを使う。

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(.ParameterName | contains("character") or contains("collation"))'

# 上と同じ
$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(contains({ParameterName: "character"}) or contains({ParameterName: "collation"}))'
例5

今までものをすべて組み合わせたもの。

desc, name, value のみ

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '.Parameters[] | select(.ParameterName | contains("character") or contains("collation")) | { desc: .Description, name: .ParameterName, value: .ParameterValue }'

{
  "desc": "Don't ignore character set information sent by the client.",
  "name": "character-set-client-handshake",
  "value": null
}
{
  "desc": "The character set for statements that arrive from the client.",
  "name": "character_set_client",
  "value": "utf8mb4"
}

...

key-value 形式

$ aws rds describe-db-parameters --db-parameter-group-name mydbparamgroup \
  | jq '[ .Parameters[] | select(.ParameterName | contains("character") or contains("collation")) | { key: .ParameterName, value: .ParameterValue } ] | from_entries'

{
  "character-set-client-handshake": null,
  "character_set_client": "utf8mb4",
  "character_set_connection": "utf8mb4",
  "character_set_database": "utf8mb4",
  "character_set_filesystem": null,
  "character_set_results": "utf8mb4",
  "character_set_server": "utf8mb4",
  "collation_connection": "utf8mb4_general_ci",
  "collation_server": "utf8mb4_general_ci",
  "skip-character-set-client-handshake": "0"
}

参考:

jq Manual
http://stedolan.github.io/jq/manual/
軽量JSONパーサー『jq』のドキュメント:『jq Manual』をざっくり日本語訳してみました | Developers.IO
http://dev.classmethod.jp/tool/jq-manual-japanese-translation-roughly/
ゼロから始めるjqチュートリアル - JSONを解析/自在に出力する
http://blog.serverfrog.jp/jq-tutorial/
jq コマンドを使う日常のご紹介 - Qiita
http://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af

EB CLI 3.x を使って Elastic Beanstalk に Rails アプリをデプロイする

環境:
Mac
eb-cli 3.0.10
ruby 2.1.5
rails 4.2.0

今回は、Elastic Beanstalk 用のコマンドラインツールである EB CLI を使って Rails アプリをデプロイしてみる。

最近出た EB CLI 3系はコマンドが2系から大幅に変更されている。
現時点(2015/2/12)では3系に対応した日本語ドキュメントはまだ用意されていないので英語の方を参照する。
http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-reference-eb.html

コマンド一覧を見てみると Heroku に似てきた気がする。

EB CLI のインストール手順は省略。homebrew でインストールした。

1. Ruby のサンプルアプリケーションをデプロイする

まずは、Elastic Beanstalk 側で用意してあるサンプルアプリケーションをデプロイしてみる。
カレントディレクトリにソースコードを何も用意していないと自動的にこのサンプルアプリケーションがデプロイされる。

Beanstalk 環境設定 (eb init)

最初にeb initコマンドを実行し、対話形式で入力していく。
eb の初回起動時にはAWS Access Key IDAWS Secret Access Keyを入力する。
たぶん IAM でAWSElasticBeanstalkFullAccess権限を持たせたユーザのものを入力すれば大丈夫なはず(自分はAdministratorAccessのユーザを使ったが)。

続いて、アプリケーション名入力、プラットフォームの選択、SSH セットアップを行う。

  • 現時点(2015/2/12)で Beanstalk で使用できる Ruby のバージョンは 2.1.5 まで。2.2.0 は使えない。
  • EC2 インスタンスSSH でログインするためのキーペアを作成して公開鍵をアップロードしてくれる。
  • デフォルトだと~/.sshディレクトリにaws-ebaws-eb.pubという名前で作成される。
$ mkdir my-hello-app && cd my-hello-app

$ eb init

Enter Application Name
(default is "my-hello-app"):  # <= アプリケーション名の入力。未入力だとディレクトリ名となる
Application my-hello-app has been created.

Select a platform.
1) PHP
2) Node.js
3) IIS
4) Tomcat
5) Python
6) Ruby
7) Docker
8) GlassFish
9) Go
(default is 1): 6  # <= Ruby 選択

Select a platform version.
1) Ruby 2.1 (Puma)
2) Ruby 2.1 (Passenger Standalone)
3) Ruby 2.0 (Puma)
4) Ruby 2.0 (Passenger Standalone)
5) Ruby 1.9.3
(default is 1): 1  # <= Ruby 2.1.5 & Puma を選択
Do you want to set up SSH for your instances?
(y/n): y  # <= SSHのセットアップを行う

Type a keypair name.
(Default is aws-eb):  # <= 未入力
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):  # <= 未入力
Enter same passphrase again:
Your identification has been saved in /Users/xyk/.ssh/aws-eb.
Your public key has been saved in /Users/xyk/.ssh/aws-eb.pub.

...

WARNING: Uploaded SSH public key for "aws-eb" into EC2 for region ap-northeast-1.

この時点でカレントディレクトリに.elasticbeanstalk/config.ymlが作成される。

$ cat .elasticbeanstalk/config.yml
branch-defaults:
  default:
    environment: null
global:
  application_name: my-hello-app
  default_ec2_keyname: aws-eb
  default_platform: Ruby 2.1 (Puma)
  default_region: null
  profile: null

GUIの管理画面を見てみるとアプリケーション「my-hello-app」が作成されているのが確認できる。

Beanstalk 環境作成、アプリのデプロイ (eb create)

続いてeb createを実行する。
環境名と CNAME を入力すると Beanstalk リソース群のプロビジョニングが開始される。

  • デプロイするコースコードを zip に固めて S3 に格納する(バージョン管理される)
  • ELB の作成
  • セキュリティグループ作成
  • Auto Scaling グループの作成
  • CloudWatch の Alarm 作成
  • EC2 インスタンス(t1.micro)の生成とアプリのデプロイ

といったことが行われる。これらが作成できるまでしばらく時間がかかる。

Beanstalk のアーキテクチャここを参照。

$ eb create
Enter Environment Name
(default is my-hello-app-dev): # <= 環境名の入力。未入力だと「アプリケーション名-dev」になる
Enter DNS CNAME prefix
(default is my-hello-app-dev): # <= CNAME の入力。デフォルト選択をしようとしたが既に使用済みだったのでエラーとなった。
That cname is not available. Please choose another
Enter DNS CNAME prefix
(default is my-hello-app-dev): my-hello-app-20150212 # <= 被ってない名前にする必要あり
WARNING: The current directory does not contain any source code. Elastic Beanstalk is launching the sample application instead.
Environment details for: my-hello-app-dev
  Application name: my-hello-app
  Region: ap-northeast-1
  Deployed Version: None
  Environment ID: e-pdxw4mbwye
  Platform: 64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Puma)
  Tier: WebServer-Standard-
  CNAME: my-hello-app-20150212.elasticbeanstalk.com
  Updated: 2015-02-12 10:15:36.529000+00:00
Printing Status:
INFO: createEnvironment is starting.
INFO: Using elasticbeanstalk-ap-northeast-1-123456789012 as Amazon S3 storage bucket for environment data.
INFO: Created security group named: sg-57ec5a32
INFO: Created load balancer named: awseb-e-p-AWSEBLoa-PCXS7HIVUX8D
INFO: Created security group named: awseb-e-pdxw4mbwye-stack-AWSEBSecurityGroup-IY32FV0GOCS
INFO: Created Auto Scaling launch configuration named: awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingLaunchConfiguration-3YG1CAINBBRQ
INFO: Created Auto Scaling group named: awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P
INFO: Waiting for EC2 instances to launch. This may take a few minutes.
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:00f26acf-6a88-471a-8134-4f5eade5a324:autoScalingGroupName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P:policyName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingScaleDownPolicy-1U1W1179JQYO7
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:f8ab2930-6e5a-4b93-ad5c-432d757b1042:autoScalingGroupName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P:policyName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingScaleUpPolicy-JKXHVAQQK4R3
INFO: Created CloudWatch alarm named: awseb-e-pdxw4mbwye-stack-AWSEBCloudwatchAlarmLow-7MXXI4P9QOVN
INFO: Created CloudWatch alarm named: awseb-e-pdxw4mbwye-stack-AWSEBCloudwatchAlarmHigh-1K56EXRQ89PIO
INFO: Added EC2 instance 'i-7ec12766' to Auto Scaling Group 'awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P'.
INFO: Application available at my-hello-app-20150212.elasticbeanstalk.com.
INFO: Successfully launched environment: my-hello-app-dev

ちなみにeb createの引数オプションで--sampleを指定しても強制的にサンプルアプリケーションのデプロイとなる。

$ eb create my-hello-app-dev --sample

ここまでで Beanstalk 環境構築とサンプルアプリケーションのデプロイが完了した。非常に簡単。

サンプルアプリケーションの確認

eb openコマンドでデプロイされたサンプルアプリケーションをブラウザで開く。

$ eb open

URL は (CNAME prefix).elasticbeanstalk.com となる。
今回の場合だと
http://my-hello-app-20150212.elasticbeanstalk.com
となる。

この時点でconfig.ymlは以下のようになっている。

$ cat .elasticbeanstalk/config.yml
branch-defaults:
  default:
    environment: my-hello-app-dev
global:
  application_name: my-hello-app
  default_ec2_keyname: aws-eb
  default_platform: Ruby 2.1 (Puma)
  default_region: null
  profile: null

Beanstalk 環境の削除 (eb terminate)

eb terminateコマンドを実行すると上記で作成した Beanstalk のリソース群がすべて削除される。

$ eb terminate
The environment "my-hello-app-dev" and all associated instances will be terminated.
To confirm, type the environment name: my-hello-app-dev # <= 確認のため削除対象の環境名の入力。
INFO: terminateEnvironment is starting.
INFO: Deleted CloudWatch alarm named: awseb-e-pdxw4mbwye-stack-AWSEBCloudwatchAlarmLow-7MXXI4P9QOVN
INFO: Deleted CloudWatch alarm named: awseb-e-pdxw4mbwye-stack-AWSEBCloudwatchAlarmHigh-1K56EXRQ89PIO
INFO: Deleted Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:00f26acf-6a88-471a-8134-4f5eade5a324:autoScalingGroupName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P:policyName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingScaleDownPolicy-1U1W1179JQYO7
INFO: Deleted Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:f8ab2930-6e5a-4b93-ad5c-432d757b1042:autoScalingGroupName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P:policyName/awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingScaleUpPolicy-JKXHVAQQK4R3
INFO: Waiting for EC2 instances to terminate. This may take a few minutes.
INFO: Deleted Auto Scaling group named: awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingGroup-1NJ3F09C0837P
INFO: Deleted Auto Scaling launch configuration named: awseb-e-pdxw4mbwye-stack-AWSEBAutoScalingLaunchConfiguration-3YG1CAINBBRQ
INFO: Deleted security group named: awseb-e-pdxw4mbwye-stack-AWSEBSecurityGroup-IY32FV0GOCS
INFO: Deleted load balancer named: awseb-e-p-AWSEBLoa-PCXS7HIVUX8D
INFO: Deleted security group named: sg-57ec5a32
INFO: Deleting SNS topic for environment my-hello-app-dev.
INFO: terminateEnvironment completed successfully.

アプリケーション枠の削除は GUI からやるしかないっぽい。


2. 自作の Rails アプリをデプロイ

次は実際に自分で作成した Rails アプリをデプロイしてみる。

Rails アプリケーション作成

プロジェクト新規作成

$ bundle exec rails new fooapp --skip-bundle
$ cd fooapp

# アプリケーションサーバとして Puma を選択するので Gemfile に追加
$ echo "gem 'puma'" >> Gemfile

# gem インストール
$ bundle install --path vendor/bundle

コントローラ作成

bin/rails g controller welcome

ビュー作成

app/views/welcome/index.html.erb

<h2>Hello World</h2>
<p>
  The time is now: <%= Time.now %>
</p>

ルート追加

config/routes.rb

Rails.application.routes.draw do
  root 'welcome#index'
end

ローカルで動作確認

$ bin/rails s

Git リポジトリを作成してソースコードをコミットする。

$ git init && git add -A && git commit -m "first commit"

Beanstalk 環境設定 (eb init)

$ eb init

Enter Application Name
(default is "fooapp"): # <= アプリケーション名の入力。未入力だとディレクトリ名となる
Application fooapp has been created.

It appears you are using Ruby. Is this correct?
(y/n): y # <= カレントディレクトリの状態から Rails アプリケーションであると認識してくれるので YES

Select a platform version.
1) Ruby 2.1 (Puma)
2) Ruby 2.1 (Passenger Standalone)
3) Ruby 2.0 (Puma)
4) Ruby 2.0 (Passenger Standalone)
5) Ruby 1.9.3
(default is 1): 1  # <= Ruby 2.1.5 & Puma を選択
Do you want to set up SSH for your instances?
(y/n): y # <= SSHのセットアップを行う

Select a keypair.
1) aws-eb
2) [ Create new KeyPair ]
(default is 2): 1  # <= キーペアは前回作成したものを使いまわす

またはeb initコマンドに引数オプションで指定してもよい。

$ eb init fooapp \
--platform "Ruby 2.1 (Puma)" \
--keyname aws-eb

.gitignore が更新されるので確認。

$ git diff

diff --git a/.gitignore b/.gitignore
index 050c9d9..964c20f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,8 @@
 /log/*
 !/log/.keep
 /tmp
+
+# Elastic Beanstalk Files
+.elasticbeanstalk/*
+!.elasticbeanstalk/*.cfg.yml
+!.elasticbeanstalk/*.global.yml

コミット。

$ git commit -am "updated .gitignore"

Beanstalk 環境作成、アプリのデプロイ (eb create)

$ eb create
Enter Environment Name
(default is fooapp-dev): # <= 環境名の入力。未入力だと「アプリケーション名-dev」になる
Enter DNS CNAME prefix
(default is fooapp-dev): fooapp-dev-20150212 # <= CNAME の入力
Environment details for: fooapp-dev
  Application name: fooapp
  Region: ap-northeast-1
  Deployed Version: 61ce
  Environment ID: e-xmzmibygyi
  Platform: 64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Puma)
  Tier: WebServer-Standard-
  CNAME: fooapp-dev-20150212.elasticbeanstalk.com
  Updated: 2015-02-12 12:14:39.613000+00:00
Printing Status:
INFO: createEnvironment is starting.
INFO: Using elasticbeanstalk-ap-northeast-1-123456789012 as Amazon S3 storage bucket for environment data.
INFO: Created security group named: sg-8ad462ef
INFO: Created load balancer named: awseb-e-x-AWSEBLoa-IZJ7KWMA0A30
INFO: Created security group named: awseb-e-xmzmibygyi-stack-AWSEBSecurityGroup-1D7SIRQQT32SW
INFO: Created Auto Scaling launch configuration named: awseb-e-xmzmibygyi-stack-AWSEBAutoScalingLaunchConfiguration-C6SUL1CBOYNE
INFO: Waiting for EC2 instances to launch. This may take a few minutes.
INFO: Created Auto Scaling group named: awseb-e-xmzmibygyi-stack-AWSEBAutoScalingGroup-1UK527EJKFE1G
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:667e6f8b-e3b7-4d1f-af92-2007c9026687:autoScalingGroupName/awseb-e-xmzmibygyi-stack-AWSEBAutoScalingGroup-1UK527EJKFE1G:policyName/awseb-e-xmzmibygyi-stack-AWSEBAutoScalingScaleDownPolicy-18233HAQI73WN
INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:8415d59d-b349-4df6-8f84-e7f6963b0fa5:autoScalingGroupName/awseb-e-xmzmibygyi-stack-AWSEBAutoScalingGroup-1UK527EJKFE1G:policyName/awseb-e-xmzmibygyi-stack-AWSEBAutoScalingScaleUpPolicy-1I30QUM7E5BWU
INFO: Created CloudWatch alarm named: awseb-e-xmzmibygyi-stack-AWSEBCloudwatchAlarmHigh-1GM6DNUY8LMMB
INFO: Created CloudWatch alarm named: awseb-e-xmzmibygyi-stack-AWSEBCloudwatchAlarmLow-1DXV8TCCTG40H
INFO: Added EC2 instance 'i-67dc3a7f' to Auto Scaling Group 'awseb-e-xmzmibygyi-stack-AWSEBAutoScalingGroup-1UK527EJKFE1G'.
INFO: Application available at fooapp-dev-20150212.elasticbeanstalk.com.
INFO: Successfully launched environment: fooapp-dev

またはeb createコマンドに引数オプションで指定してもよい。

eb create fooapp-dev --cname fooapp-dev-20150212

ブラウザで確認。

$ eb open

ブラウザで開いてみると画面に以下のようなエラーが出た。

An unhandled lowlevel error occured. The application logs may have details.

eb logsコマンドでサーバ側のログを確認する。

$ eb logs

...

-------------------------------------
/var/log/puma/puma.log
-------------------------------------
=== puma startup: 2015-02-12 12:22:27 +0000 ===
=== puma startup: 2015-02-12 12:22:27 +0000 ===
[20244] - Worker 0 (pid: 20248) booted, phase: 0
2015-02-12 12:24:17 +0000: Rack app error: #<RuntimeError: Missing `secret_token` and `secret_key_base` for 'production' environment, set these values in `config/secrets.yml`>

...

production 環境時のsecret_key_baseを設定し忘れていたため、エラーが出ていた。
config/secrets.yml では以下のようにsecret_key_base環境変数から取得して設定している。

# config/secrets.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

Beanstalk の環境変数eb printenvコマンドで確認してみる。

$ eb printenv
 Environment Variables:
     AWS_SECRET_KEY = None
     RAILS_SKIP_ASSET_COMPILATION = false
     BUNDLE_WITHOUT = test:development
     RACK_ENV = production
     PARAM5 = None
     PARAM4 = None
     PARAM3 = None
     PARAM2 = None
     PARAM1 = None
     RAILS_SKIP_MIGRATIONS = false
     AWS_ACCESS_KEY_ID = None

これらの環境変数は Beansstalk のプラットフォームが Ruby の場合に Beansstalk 側であらかじめ用意してくれるパラメータとなる。

eb setenvコマンドで環境変数SECRET_KEY_BASEを追加する。

$ eb setenv SECRET_KEY_BASE=`bin/rake secret`
INFO: Environment update is starting.
INFO: Updating environment fooapp-dev's configuration settings.
INFO: Successfully deployed new configuration to environment.
INFO: Environment update completed successfully.

確認。

$ eb printenv
 Environment Variables:
     AWS_SECRET_KEY = None
     RAILS_SKIP_ASSET_COMPILATION = false
     SECRET_KEY_BASE = 87cea6cb25eba020d7c5b51d0b20377a29eaa6df475f5e1cef41f31eb67b2b0c7a932902afc3f5b44107439d0f5e7a02a25db33043a81a952190dbfde7fbff8c
     RACK_ENV = production
     PARAM5 = None
     PARAM4 = None
     PARAM3 = None
     PARAM2 = None
     PARAM1 = None
     BUNDLE_WITHOUT = test:development
     RAILS_SKIP_MIGRATIONS = false
     AWS_ACCESS_KEY_ID = None

再度、ブラウザで確認してみると今度は正しく表示された。

$ eb open

このeb setenvコマンドは v3 から追加されたものでこれによってコマンドから環境変数が設定できるようになった。
設定できるタイミングはeb createの実行後となる。

ちなみに v2 ではコマンドから環境変数を設定できず、GUI からやるか、以下のようなオプションファイルを用意することで環境変数を設定できたけど、このファイルは git で管理する必要があった。

# .ebextensions/01env.config
option_settings:
  - namespace: aws:elasticbeanstalk:application:environment
    option_name: SECRET_KEY_BASE
    value: xxxxxxx

という問題があってちょっと困っていたのでこれが解決できてよかった。

再デプロイ (eb deploy)

ソースコードの修正を反映するには、 git にコミット後にeb deployコマンドを実行する。

eb deploy

次回は VPC 内に Beanstalk 環境を構築したり RDS と連携する方法をやってみる。

Rails でリクエストの HTTP ヘッダを取得してログに出力する

環境:rails 4.2.0

Rails でリクエストの HTTP ヘッダはrequest.headersから取得できる。

すべてログに出力するなら

# すべてログに出力する
request.headers.sort.map { |k, v| logger.info "#{k}:#{v}" }

個別に取得するなら

# ユーザーエージェントを取得する
request.headers[:HTTP_USER_AGENT]

request.envからでも取得できる。

# ユーザーエージェントを取得する
request.env['HTTP_USER_AGENT']

今回やりたいことはクライアント側から HTTP ヘッダに API のバージョンを埋め込んでリクエストしてくるので、すべてのリクエストの API バージョンを取り出してログに出力したい。

リクエストのパラメータを curl で再現すると以下のようなかんじ。

curl localhost:3000 -H 'API_VERSION: 1.0'

共通処理として書きたいのでapplication_controllerのフィルタで書く。
HTTP ヘッダのパラメータ名にはHTTP_という prefix が自動的に付与されるようなので忘れずに追加する。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception

  before_action :http_header_log

  private

    def http_header_log
      logger.info("api_version:#{request.headers[:HTTP_API_VERSION]}")
    end

end

これですべてのリクエストに対してログ出力できるようになった。

Lograge を使う

続いてlogrageという gem を使って HTTP ヘッダを出力する方法もやってみた。

roidrage/lograge
https://github.com/roidrage/lograge

この gem は Rails のログ出力フォーマットをカスタマイズするもの。
Rails のデフォルトのログは複数行出力されるが、これを使うと Apache のように1リクエスト毎1行で出力されるようになるので見やすい。

gem 追加

gem 'lograge'

反映させたい環境の config に以下を追加すればOK。

# config/environments/production.rb

    config.lograge.enabled = true

今回は HTTP ヘッダのAPI_VERSIONをログ出力に追加するため、さらに以下の設定を追加する。

まずappend_info_to_payloadメソッドを追加し、そこで HTTP ヘッダよりAPI_VERSIONを取得してpayloadハッシュに入れる。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception

  def append_info_to_payload(payload)
    super

    payload[:api_version] = request.headers[:HTTP_API_VERSION]
  end

end

そして config にcustom_optionsでログ出力に追加するものを指定する。
timeはデフォルトで表示されないのでこれも追加しておく。

# config/environments/production.rb

    config.lograge.enabled = true

    config.lograge.custom_options = lambda do |event|
      {:time => event.time, :api_version => event.payload[:api_version]}
    end

これで以下のようにログ出力されるようになった。

method=GET path=/ format=*/* controller=articles action=index status=200 duration=88.37 view=87.28 db=0.11 time=2015-02-01 17:39:35 +0900 api_version=1.0

ClearDB に文字コード utf8mb4 で保存する

環境:

ruby: 2.2.0  
Rails: 4.1.8  
ClearDB(MySQL): 5.5.40  

Heroku の MySQL アドオン「ClearDB」で文字コードutf8mb4で保存する方法についてメモ。

Rails + MySQLutf8mb4を扱う方法は以前書いた。

ClearDB ではデフォルトのキャラクタセットは以下のように UTF8 になっている。

mysql> show create database heroku_1234567890abcde;
+------------------------+---------------------------------------------------------------------------------+
| Database               | Create Database                                                                 |
+------------------------+---------------------------------------------------------------------------------+
| heroku_1234567890abcde | CREATE DATABASE `heroku_1234567890abcde` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+------------------------+---------------------------------------------------------------------------------+

mysql> show variables like 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | utf8   |
| character_set_connection | utf8   |
| character_set_database   | utf8   |
| character_set_filesystem | binary |
| character_set_results    | utf8   |
| character_set_server     | latin1 |
| character_set_system     | utf8   |
+--------------------------+--------+

絵文字を扱いたいのでutf8mb4に変更したい。
しかしmy.cnfは触れないし、RDS の DB パラメーターグループのようなもので変更できる手段もなさそう。

なので ActiveRecord が発行するテーブルの create 文にオプションでキャラクタセットを追加してテーブル単位にutf8mb4を設定するように、またクライアント側の接続 URL のキャラクタエンコーディングutf8mb4を追加して対応した。

ClearDB を使うまでの手順は以前書いたとおりに進める。

1. DBの接続URLを変更

Heroku 上の Rails アプリから DB に接続するための URL としてconfig/database.ymlに書かれた設定ではなく Heroku の環境変数DATABASE_URLが使用される。なのでこの URL にencoding=utf8mb4を追加する。

変更前:

$ heroku config | grep DATABASE_URL
CLEARDB_DATABASE_URL:  mysql://<username>:<password>@<host>/<database>?reconnect=true
DATABASE_URL:          mysql2://<username>:<password>@<host>/<database>?reconnect=true

変更:

$ heroku config:set DATABASE_URL="mysql2://<username>:<password>@<host>/<database>?reconnect=true&encoding=utf8mb4"

COLLATE も指定する場合

$ heroku config:set DATABASE_URL="mysql2://<username>:<password>@<host>/<database>?reconnect=true&encoding=utf8mb4&collation=utf8mb4_general_ci"

変更後:

$ heroku config | grep DATABASE_URL
CLEARDB_DATABASE_URL:  mysql://<username>:<password>@<host>/<database>?reconnect=true
DATABASE_URL:          mysql2://<username>:<password>@<host>/<database>?reconnect=true&encoding=utf8mb4

2. テーブル作成のマイグレーションファイルにオプション追加

以下のようにマイグレーションファイルのcreate_tableしている部分にオプションとして CHARSET を追加する。

class CreateArticles < ActiveRecord::Migration
  def change

    create_table :articles, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' do |t|
    # COLLATE も指定する場合
    # create_table :articles, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci' do |t|
      t.string :title
      t.string :content

      t.timestamps
    end
  end
end

3. ClearDB にテーブルを作成

変更をコミットして Heroku にプッシュしてデプロイする。
デプロイ完了後、テーブルがまだ未作成ならheroku run rake db:migrateで作成、作成済みならheroku run rake db:migrate:resetを実行して ClearDB にテーブルを再作成する。

ClearDB に直接接続する

$ mysql -u <username> -p -h <host> -D <database>

確認

mysql> show create database heroku_1234567890abcde;

+------------------------+------------------------------------------------------------------------------------+
| Database               | Create Database                                                                    |
+------------------------+------------------------------------------------------------------------------------+
| heroku_1234567890abcde | CREATE DATABASE `heroku_1234567890abcde` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ |
+------------------------+------------------------------------------------------------------------------------+


mysql> show create table articles;

CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `content` varchar(255) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4


mysql> show variables like 'character\_set\_%';
+--------------------------+---------+
| Variable_name            | Value   |
+--------------------------+---------+
| character_set_client     | utf8    |
| character_set_connection | utf8    |
| character_set_database   | utf8mb4 |
| character_set_filesystem | binary  |
| character_set_results    | utf8    |
| character_set_server     | latin1  |
| character_set_system     | utf8    |
+--------------------------+---------+

これでutf8mb4で保存できるようになった。

cat, less コマンドの表示を Syntax Highlight させる

環境: Mac

Pygments という Python のパッケージがあり、その中の pygmentize コマンドラインツールを使うことでシンタックスハイライト表示できるようになる。

Pygments インストール

まずは Pygments のインストール。

$ pip install Pygments

または

$ sudo easy_install Pygments

lessfilter の作成

次にlessfilterという名前でシェルスクリプトファイルを作成してパスが通っている場所に置く。

# パス確認
$ printenv PATH

/usr/local/bin/配下に作ることにする。

$ vi /usr/local/bin/lessfilter
$ chmod 755 /usr/local/bin/lessfilter

lessfilter

#!/bin/bash
case "$1" in
    *.sh|*.awk|*.diff|*.patch|*.sql|\
    *.php|*.pl|*.pm|*.py|*.rb|*.haml|*.slim|\
    *.java|*.groovy|*.scala|*.clj|*.cljs|\
    *.c|*.h|*.m|*.swift|*.go|*.lua|*.hs|*.erl|*.fs|*.ml|*.exs|\
    *.html|*.js|*.coffee|*.css|*.sass|*.scss|\
    *.json|*.xml|*.yaml|*.yml)
        pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -g "$1"
        ;;
    .zshrc|.bashrc|.bash_aliases|.bash_environment)
        pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l sh "$1"
        ;;
    Gemfile|Rakefile)
        pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l ruby "$1"
        ;;
    *)
        grep '#!/bin/bash\|#!/bin/sh' "$1" > /dev/null
        if [ "$?" -eq "0" ]; then
            pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l sh "$1"
        else
            exit 1
        fi
esac

exit 0

lessfilterでは pygmentize コマンドを使ってファイルを開く。
-gオプションを指定することで入力ファイル名から lexer を推測してくれる。
ここに pygments で使える lexer 一覧があるので必要な物を自分で追加してカスタマイズする。

環境変数の設定

続いて環境変数LESSLESSOPENを設定する。
zsh を使っているので.zshrcに以下を追加。bash なら.bashrcなどに。

# cat
alias c='pygmentize -O style=monokai -f terminal256 -g -O encoding=utf-8'
function cl() {
    c $1 | nl -n ln -b a
}
alias cl=cl

# less
export LESS='-R'
export LESSOPEN='|lessfilter %s'

反映

$ source ~/.zshrc

使い方

# cat のシンタックスハイライト
$ c foo.rb

# cat + 行番号表示
$ cl foo.rb

# less のシンタックスハイライト
$ less foo.rb

通常の cat コマンドとは別にcclいうエイリアスで設定する。
こちらを使うとシンタックスハイライト表示になる。

こちらの記事を参考にしました。

catやlessをシンタックスハイライト+行番号表示してコードを見やすくする - Qiita
http://qiita.com/zaru/items/4a7811ac21f74a13481c
linux - Get colors in 'less'' command - Super User
http://superuser.com/questions/117841/get-colors-in-less-command


追記

lessfilterを修正した。

lessfilter

#!/bin/bash

case `basename $1` in
    *.log)
        ;;
    .zshrc)
        pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l sh "$1"
        ;;
    Gemfile|Podfile)
        pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l ruby "$1"
        ;;
    *)
        head -n 1 "$1" | grep '#!/bin/bash\|#!/bin/sh' > /dev/null
        if [ "$?" -eq "0" ]; then
            pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -l sh "$1"
        else
            pygmentize -O encoding=utf-8 -O style=monokai -f terminal256 -g "$1"
        fi
        ;;
esac
  • 拡張子ホワイトリスト方式は止めた。-gでよしなにやってくれるので
  • case文の条件がフルパスだとマッチしてなかったので修正した
  • .bash 系のファイルとか Rakefile は lexer で対象になっていたので削除
  • シバン(shebang)のチェックは1行目のみを対象
  • .logファイルはスキップするようにした

-gで開く場合、拡張子に対応する lexer がない場合はてっきりシンタックスハイライトは適用されないと思っていたが、そういう場合は text とみなされ TextLexer が適用されるみたい。で、ログファイルのようなでかいファイルを開くときにもシンタックスハイライトしようとして解析に時間がかかって開けなくて困った。なのでとりあえず個別に除外するようにした。他にもあればここに追加していく。

ちなみに-gでどの lexer が適用されるかは-Nオプションで確認することができる。

$ pygmentize -N config/application.rb
rb
$ pygmentize -N log/development.log
text