production.log

ピクスタ株式会社で開発部の部長をやっている星直史のブログです。

AWS EC2のルートボリューム(EBS)をダウンタイム0で拡張する方法

概要

blog.naoshihoshi.com

サービスを公開したと同時に、mackerelを導入してみました。 CPU, Memory, filesystemのアラートをデフォルトの閾値で設定した結果、一瞬でfilesystemのアラートが鳴りました。

f:id:watasihasitujidesu:20180727130724p:plain

すぐさま対応できなかったので、とりあえずアラートを止めるために閾値を変えるというワークアラウンド対応をしました(白目)

ただ、ディスク容量の75%ほど使用していたので、根本的に対応しなければサービス断になると思われるため、ボリューム拡張をすることにしました。 今回はAWS EC2のルートボリューム(EBS)をダウンタイム0で拡張する方法について書こうと思います。

手順

AWSマネコンより、EBSのストレージ拡張

f:id:watasihasitujidesu:20180727132326p:plain
何はともあれ、AWS マネジメントコンソール > EC2 > ELASTIC BLOCK STORE > Volumes画面に行きましょう。 Actionsプルダウンを押下し、Modify Volumeを選択します。

f:id:watasihasitujidesu:20180727132331p:plain
続いて、任意のVolume TypeとSizeを選びます。 今回は、Volume Typeの変更はなく、Sizeを8GiBから16GiBに拡張します。 そして、Modifyボタンを押下。

f:id:watasihasitujidesu:20180727132337p:plain
すると、アラート的なものが表示されます。

Are you sure that you want to modify volume vol-xxxxxxxxxxxxx?
It may take some time for performance changes to take full effect.
You may need to extend the OS file system on the volume to use any newly-allocated space.
Learn more about resizing an EBS volume on Linux and Windows.

=> vol-xxxxxxxxxxxxxを変更してもよろしいですか?
=> パフォーマンスの変更が有効になるまでには時間がかかることがあります。
=> 新しく割り当てられた領域を使用するには、ボリューム上のOSファイルシステムを拡張する必要があります。
=> LinuxおよびWindowsでのEBSボリュームのサイズ変更の詳細については、こちらをご覧ください。

とのこと。Yes!!!

f:id:watasihasitujidesu:20180727132501p:plain
モーダルが閉じたところで、念のため、Sizeが変わったことを確認します。 また、Stateがin-use - completed(100%)になるまで少し待ちましょう。

AWS マネジメントコンソール上の操作は以上です。

EC2インスタンスにSSH接続し、コンソール上で変更の反映

さきほど出てきたアラートの通り、インスタンスにSSHアクセスし、コンソール上からボリューム拡張を反映させる必要があります。

使用しているファイルシステムの確認

file -sコマンドで使用しているファイルシステムを確認します。

$ sudo file -s /dev/xvd*
/dev/xvda:  DOS/MBR boot sector; ......
/dev/xvda1: Linux rev 1.0 ext4 filesystem data, .....

Linux ext4 ファイルシステムと DOS/MBR boot sectorというファイルシステムなのがわかりました。

物理ディスクの確認

インスタンスにアタッチされたブロックデバイスを確認するためにlsblkコマンドを使います。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  16G  0 disk 
└─xvda1 202:1    0   8G  0 part /

おぉ〜、しっかり16Gibがアタッチされていますね。
この情報から、ルートボリュームである/dev/xvdf1 は、16 GiB のデバイス(/dev/xvda)に含まれる 8 GiB のパーティションでだということがわかります。
また、この/dev/xvdaはパーティションは/dev/xvdf1以外ありません。そのため、ボリュームの残りの領域を使用するために、パーティションのサイズを変更する必要があるということがわかります。

パーティションのサイズ拡張

growpartコマンドでパーティションのサイズ拡張をします。

$ sudo growpart /dev/xvda 1
CHANGED: disk=/dev/xvda partition=1: start=4096 old: size=16773086,end=16777182 new: size=33550302,end=33554398

再び、lsblkコマンドで、意図した変更になっているか確認します。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  16G  0 disk 
└─xvda1 202:1    0  16G  0 part /

良さそうですね。

ファイルシステム上のディスク領域の変更

このままでは、ディスクのパーティションが切れただけで、ファイルシステムに変更が反映されていません。

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         488M   56K  488M    1% /dev
tmpfs            497M     0  497M    0% /dev/shm
/dev/xvda1       7.8G  5.5G  2.2G   72% /

Linux ext4 ファイルシステムの変更反映はresize2fsで行います。

$ sudo resize2fs /dev/xvda1
resize2fs 1.42.12 (29-Aug-2014)
Filesystem at /dev/xvda1 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/xvda1 is now 4193787 (4k) blocks long.

最後に変更がうまく行っているか、確認します。

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         488M   56K  488M    1% /dev
tmpfs            497M     0  497M    0% /dev/shm
/dev/xvda1        16G  5.5G   11G   36% /

/dev/xvda1のサイズ16Gibになり、使用率が72% => 36%に下がりましたね!

まとめ

AWSマネジメントコンソール上でEBSのSizeをアップし、数分待つことで、容量が増えました。
また、EC2インスタンス上にSSH接続し、パーティションの容量変更とファイルシステムへの反映を行うだけで、インスタンスを停止せずにルートボリュームの拡張を行うことができました。
インスタンス停止せずにボリューム拡張できるのは素晴らしいですね!

mackerelのグラフも、ぴょこんと上がりました! f:id:watasihasitujidesu:20180727140430p:plain

mackerelをAWS OpsWorks カスタムクックブックでインストールする方法

概要

これまで、Webサービスを作ったことはあれど、サーバーの管理/監視は構築したことがありませんでした。
手軽に導入したかったので、SaaS型サーバー監視サービスを導入しようと考えていました。
そこで、今回はmackerelを使って監視をしようと思います。また、AWS OpsWorksのカスタムクックブックでプロビジョニングしていたので、mackerelをAWS OpsWorks カスタムクックブックでインストールする方法について書こうと思います。

スタートアップガイドを読み、コード化対象のスクリプトを確認

何はともあれ、スタートアップガイドを読みます。今回はAmazonLinuxを使用しているので、それ用のガイドを読みます。

mackerel.io

ステップは超絶簡単ですね。

curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum.sh | MACKEREL_APIKEY='<YOUR_API_KEY>' sh
echo "apikey = '<YOUR_API_KEY>'" >> /etc/mackerel-agent/mackerel-agent.conf
sudo /sbin/service mackerel-agent start

こんだけ!

手順

API Keyの確認

まずはmackerelのダッシュボードでAPIキーの確認をします。

Organization > APIキータブ から確認します。

f:id:watasihasitujidesu:20180718074336p:plain

Chefのレシピ作成

AWS OpsWorks スタック におけるCustom Chef cookbooksを適用する方法 - production.log

こちらを参考にレシピを作っていきます。 レシピはスタートガイドにある通り、3つのコマンドを実行するだけのシンプルなものです。

api_key = node[:deploy][:testrooper][:mackerel_apikey]

execute "mackerel install" do
  user "root"
  command "curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum.sh | MACKEREL_APIKEY='#{api_key}' sh"
  action :run
end

bash "add api key" do
  not_if 'grep "apikey" /etc/mackerel-agent/mackerel-agent.conf'
  code <<-EOC
    echo "apikey = '#{api_key}'" >> /etc/mackerel-agent/mackerel-agent.conf
  EOC
end

execute "run agent" do
  user "root"
  command "/sbin/service mackerel-agent start"
  action :run
end

こちらのコードはGitで管理しているわけですが、APIキーをコードに直接書かないようにしています。
何も知らずにPublicリポジトリにしちゃったりなんだりした場合に、意図せずインターネットにAPIキーが晒されてしまうためです。
そのため、APIキーはOpsWorksのCustomJSONから取得するようにしています。

AWS OpsWorksでCustomJSONの設定

f:id:watasihasitujidesu:20180718074401p:plain
先述の通り、AWS OpsWorksのCustomJSONに記述したAPIキーを使ってプロビジョニングするので、その設定をします。

AWS OpsWorks > Stack > Stack Settings > Edit

{
    "deploy": {
        "testrooper": {
            "mackerel_apikey": "xxxxxxxxxxxxxxx"
        }
    }
}

Execute Recipes

最後に、カスタムクックブックを実行して終了です。
ステップは、Deployments > RunCommandで下記2つを実行するだけです。

  • Update Custom Cookbooksを実行
  • Execute Recipesを実行

f:id:watasihasitujidesu:20180718074431p:plainf:id:watasihasitujidesu:20180718074433p:plain

結果の確認

f:id:watasihasitujidesu:20180718074444p:plain
無事にmackerelのagentが動いていれば、ダッシュボードに1ホストだけACTIVEになっているはずです。

まとめ

無事にmackerelをAWS OpsWorks カスタムクックブックでインストールできました👏
そもそもの導入が簡単というのもありますが、手順をコード化することで、今後サーバーが何台増えてもレシピを実行するだけで済むようになりました。
あとは、mackerelの方で通知の設定などを行うだけで楽々とサーバー管理ができそうです!

GitHubの草を60日間連続で生やしてわかった習慣化のコツ

概要

f:id:watasihasitujidesu:20180716100634p:plain

最近個人プロジェクトの開発をしているのですが、気づけばGithubの草が60日間連続で生えていました。 60日間も毎日草を生やしていると、完全に習慣されたと思ったため、 今回はGithubの草を60日間連続で生やしてわかった習慣化のコツを紹介します。

習慣化のコツ

モチベーション管理

個人の目標における内発的動機と外発的動機の重なりの意識

f:id:watasihasitujidesu:20180716072408p:plain
自分がモチベーションを感じることとしては、学びたいことを学んでいるときや、なりたい自分に近くための学習など「やりたいこと」を実行しているときです。
「やりたいこと」と関連がある学習を行なっているときは、少なくとも2ヶ月くらいは継続的に学習ができていた記憶があります。
一方、必要に迫られた学習など、「やるべきこと」を実行しているときは、あまりモチベーションの高まりを感じることができず、長期的には習慣化できませんでした。

とはいえ、やりたいことを優先しすぎると、成果*1の最大化には繋がらないと思い、「やるべきこと」と「やりたいこと」が重なるように意識しました。

学習の対象が、学びたいものかつ、学ぶべきものだったばあい、「やるべき」と「やりたい」が重なっている状態になります。
この動機のスイートスポット*2が存在する場合は、まずはそこにフォーカスすることをおすすめします。

内発的動機に対する行動結果の紐付け

f:id:watasihasitujidesu:20180716083427p:plain

先述した学習のスイートスポット*3が存在すれば苦労しませんが、多くはないでしょう。
ただ、「やるべきことは、必ずしもやりたいことと重なるわけではないが、少なからず、好影響があるかもしれない」といったように、楽観的に考えることが重要です。
物事は、何にでもグラデーションがあるので、一見、やりたいこととは無関係でも巡り巡って自分のやりたいことに繋がるか?を考える事が重要です。
そのため、やるべきことの中から、やりたいことに近付く要素が最も多いものを取捨選択することになります。

取り得る選択始の中でモチベーションと成果が最大化されるものを吟味していきましょう!

可視化・可視化・可視化

Githubの草を生やす上で欠かせないのはGItHub-Gardenerです。

f:id:watasihasitujidesu:20180716100702p:plain

こんな感じで可視化されるのですが、各種スコアが表示されます。

consecutive days (best record updating) 60 days

特に、継続日数が表示されるこの部分ですが、強烈な強制力があります。
継続日数を増やしていく中で、下記のような嬉しさ、達成感、充実感がありました。

継続日数 嬉しみ
7 days 草が一本に繋がって単純に嬉しい
10 days 値が2桁になって嬉しい
30 days 草から芝生となった嬉しさと、謎の達成感
60 days 習慣化された充実感

目標到達点の認識とリズム作り

目標到達点の認識

先述の通り、GitHub-GardenerはGitHubの芝生を可視化ツールであり、継続日数を数値として表示します。
初心者Gardenerだった自分は、まずは3 daysを目標にしました。3 days達成すると7days, 10 days, 30 days, 60days....、と次の目標を意識することができました。
この効能は、ある程度粒度が大きい到達点(マイルストーン)を認識することで、目標達成に対する意識が芽生えさせることができることです。

また、継続日数をマイルストーンとして設定した場合、自分のリアルなスケジュールを見比べて、「○曜日は仕事の帰りが遅いから次の日の朝に影響が出そうだ。タスクの粒度を極小にしよう」といったリスク回避について考えることもできます。

目標達成に向けてのリズム作り

朝6時に起きたとしても、確保できる時間は3時間前後です。3時間しかないと、できることは多くはありません。
そこで、コミットするタスクを分解して、目標達成に向けてのリズム作りをしました。

よくある継続学習指南では、しばしば「まずは本を開くところから始めよ」「まずは5分から始めよ」というのを目にしますが、まさにこれです。

週: 平日/休日 1日あたりの作業時間 作業内容
1週目: 休日 12h 規模が大きめの実装
1週目: 平日 3h 些細なデザイン調整や規模の小さいリファクタなど、作業開始から30分以内でコミットできるような粒度のタスク
2週目: 休日 12h 技術調査と平日のためのタスク分解
2週目: 平日 3h 休日に行なった粒度の小さいタスクの実装
  • 休日は大きめの作業
  • 平日は小さめの作業

といったように、割り当てることができる時間と、タスクの粒度を合わせることで、無理のないペースで確実に成果を出すことができます。 平日は粒度は小さいタスクの積み重ねがメインとなるのですが、自身で成功体験を積み重ねることと、それを自分自身で承認することが習慣化のコツです。

その点、GitHub-Gardenerは、GitHub-Gardenerを使うことで、自然と上記が達成できるような仕組みになっていると言えます。

すごいぞ、GitHub-Gardener。

生活のリズムを一定にする

GitHub-Gardenerを使うと、草を生やしていない(commitしていない)と、謎の不安感に包まれるようになります。 これは適度な負荷であり、生活にちょっとした緊張感を与えるストレスだと思います。ストレスを力に変えましょう。

GitHub-Gardenerを使うと、朝目が覚めたときに、まず「commitしなきゃ!」と、頭に浮かぶようになりました。 普段は「眠いなぁ〜、もう少し寝ようかなぁ〜...ZZzz」ってなるのですが、この思考のインターセプトはやばいですね。 そのため、結果的に朝、目覚めと同時にcommitするようになりました。

また、朝6時に起きてcommitという生活を続けていると、スケジュールや体調に左右されずに安定して自己研鑽できることがわかりました。

  • 夜早く寝るので、睡眠時間を十分に確保できる
  • 早朝から朝日を浴びることができるので、体調がすこぶる良くなる(安定化)
  • 旅行などの予定が入っていても安定してコミットができる(スケジュールの影響を受けにくい)

素晴らしいぞ、GitHub-Gardener。

まとめ

60日間継続的にアウトプットし続けることができたので、習慣化のコツを書いてみました。

  • 自分は何にモチベーションを感じるのか、学習対象の意義は何かを考える
  • 可視化による達成度合いの認識
  • マイルストーンの設定による、長期的な目標管理と達成に向けたリズム作り
  • 持続的に成果を出すための生活基盤の構築

基本的には、何が学びたいんだっけ?何を得たいんだっけ?を軸に考えていくと継続はできるのではないかと思います。
ただ、人間は易きに流れやすい生き物であるため、ある程度、強制力や自己承認のための仕組みが必要です。
自分は補助的な役割としてGitHub-Gardenerを選びましたが、継続をするための仕組みが無駄なく全て揃った良いサービスでした。
おすすめです。

*1:習得したスキルがどこに繋がるのかといった観点や、仕事で役に立つかといった観点

*2:と、勝手に呼んでいる

*3:やりたいことと、やるべきことが重なり

【Rails】deviseが送信するメールをAWS SESから配信する方法

概要

個人開発において、Railsのdeviseから配信されるメールは、これまでGmailを使用していたのですが、せっかくドメインを取得したので、AWS SESからメールを送信することにしました。
今回はRailsにおいて、deviseが送信するメールをAWS SESから配信する方法について書きます。

前提

  • Rails 5.2.0
  • Route53でドメインを取得済みである
  • PublicIPでアクセスできるサービスが存在する

手順

SESの設定

SESはデフォルトでは特定のメールアドレスには設定さえすれば、送信することができます。
しかし、多くのWebサービスは不特定多数のユーザーにメールを送信するため、その設定を行います。

ドメイン設定

f:id:watasihasitujidesu:20180716090617p:plain
Identity Management > Domains > Verify a New Domain
Verify a New Domain を押下すると、ポップアップが出てドメインの入力を求められます。
取得したドメインを入力し、Generate DKIM Settingsもチェックします。*1

f:id:watasihasitujidesu:20180716091130p:plain
f:id:watasihasitujidesu:20180716091521p:plainf:id:watasihasitujidesu:20180716091534p:plain

Route53でドメインを取得している場合は、Use Route53ボタンを押下すれば、AWSの方で自動でRecord Setを設定してくれます。

f:id:watasihasitujidesu:20180716091941p:plain
しばらく時間をあけると
Identity Management > Domains > 登録したドメイン にアクセスするとVerificationとDKIMのステータスがverifiedになっています。

サンドボックス状態の解除

f:id:watasihasitujidesu:20180716092315p:plain
Email Sending > Sending Statisticsにアクセスすると、上記のような警告がでます。
現状、特定のメールアドレスにしか送信できない送信制限がかかっているので、サポート制限緩和申請をする必要があります。

f:id:watasihasitujidesu:20180716092940p:plainf:id:watasihasitujidesu:20180716092942p:plainf:id:watasihasitujidesu:20180716092945p:plain

上記の通り、各種項目を入力します。
1日くらいすると、AWSの審査の末、SESからメール送信が可能になります。

メール送信テスト

f:id:watasihasitujidesu:20180716093542p:plain

Identity Management > Domains > Send a Test Email から、任意のメールアドレスにメール配信が可能になります。
Send Test Mailボタンを押し、メールが受信できれば成功です。

Rails(devise)の設定

SES自体が使えるようになったので、今度はRails側の設定をしていきます。

AWS SDKをinstall

Railsでaws sdkを使用するためにGemfileに下記を追記します。

gem 'aws-ses'

ActionMailerの設定

config/initializers/aws.rbを新規で作成し、下記の通り、SESを使用するように変更します。

ActionMailer::Base.add_delivery_method(
  :ses,
  AWS::SES::Base,
  access_key_id: Rails.application.credentials.dig(:aws, :access_key_id),
  secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key),
  server: 'email.us-west-2.amazonaws.com'
)

server はSESで選択したリージョンによって異なるので 公式ドキュメントのリージョンと Amazon SES から API (HTTPS) エンドポイントを確認してください。

次に、config/environments/production.rbの設定をSESを使用するように変更します。

config.action_mailer.default_url_options = { host: 'testrooper.com' }
config.action_mailer.delivery_method = :ses

deviseの設定変更

config/initializers/devise.rbのconfig.mailer_senderを修正します。

# config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
config.mailer_sender = 'Testrooper <noreply@testrooper.com>'

Mailerクラスの修正

最後にMailerクラスのfromの設定を変更して終わりです。

# frozen_string_literal: true
 
 class ApplicationMailer < ActionMailer::Base
#  default from: "from@example.com"
   default from: "Testrooper <noreply@testrooper.com>"

まとめ

これまで説明した通り、SESの設定自体はとても簡単です。
送信制限緩和申請のために、1日程度かかってしまうのですが、特に審査に落ちることもなく使えるようになりました。
また、Railsの方の設定も、難しいことはなく、設定ファイルの修正だけでした。

これまでGmailしか使っていなかったのですが、ドメインがあるのであれば、初手でSESを選択した方が良いかもしれませんね。

*1:DKIM とは DomainKeys Identified Mail の略で、受信した電子メールが「正当な送信者から送信された改ざんされていないメール」かどうかを調べることができる電子署名方式の送信ドメイン認証技術

AWS OpsWorks スタック におけるCustom Chef cookbooksを適用する方法

概要

AWS OpsWorks スタックでは、Chef Supermarketで公開されているCookbooksを適用する方法もありますが、自前で作成したCustom Chef cookbooksを適用することもできます。

今回はAWS OpsWorks スタックにおいて、自前で作成したCustom Chef cookbooksを適用する方法を紹介します。

Custom Chef cookbooksの作成

test-chefという名前のPublicリポジトリを作成

test-chefという名前のPublicリポジトリを作成します。そして、下記のようなフォルダ構成にします。

naoshihoshi 10:03:00 test-chef$ pwd
/Users/naoshihoshi/test-chef
naoshihoshi 10:03:02 test-chef$ tree
.
└── hoge
    └── recipes
        └── default.rb

Opsworksではrecipesディレクトリ直下のdefault.rbがデフォルト呼ばれることになります。 default.rbの内容は下記の通りです。とりあえず動かしてみるだけのコードですね。

# hoge/recipes/default.rb
log "Hello Chef"

ログに"Hello Chef"と出力されれば、動作していることがわかります。

AWS OpsWorksでCustom Chef cookbooksの適用

Stack Setting

OpsWorksでスタックを追加し、さきほど作成したcookbooksを適用します。

f:id:watasihasitujidesu:20180701101743p:plain

スタック作成画面で
1. Use custom Chef cookbooksをYesに変更
2. Repository URLは先ほど作成したtest-chefのリポジトリのURLを指定

Layer Setting

Layerを追加し、Recipesの変更を行います。 今回は、Setup時に先ほどのレシピを実行させたいので、このような設定になります。
f:id:watasihasitujidesu:20180701102337p:plain

gitで管理しているリポジトリの直下にあるディレクトリを設定すれば、その配下にあるrecipesディレクトリのdefault.rbが呼び出されるという仕組みです。

naoshihoshi 10:03:02 test-chef$ tree
.
└── hoge <===== このディレクトリが設定された場合、
    └── recipes
        └── default.rb <===== これが呼び出される

インスタンスを作成し、起動

インスタンスを作成し、起動をすると、setupで設定したレシピが実行されます。 インスタンス起動後にログがみれるので、インスタンス名をクリックし、ページ下部のログのshowリンクでログを確認します。

f:id:watasihasitujidesu:20180701103741p:plain

無事にHello Chefが表示されていますね!

任意のレシピを作成し、実行する

default.rb以外に実行したい場合は任意のファイルを作成後hoge::任意のファイル名とします。

.
└── hoge
    └── recipes
        ├── configure.rb
        └── default.rb

configure.rbを作成した場合、Layerの設定ではhoge::configureとなります。

まとめ

小さなコードではありますが、OpsWorksで自前で用意したCustom Chef cookbooksを動かすことができました。 また、レシピを複数用意して、任意のレシピを実行する方法もとてもシンプルで簡単でした。 動かし方さえわかってしまえば、あとはプロビジョニングのためのコードを書き足していくだけです!

WebdriverIOを使ったChromeによるE2Eテスト環境構築の手順 - Amazon Linux -

概要

Amazon LinuxでWebdriverIOによる自動E2Eテストの実験をしようとしていたのですが、 うまくGetting startedできませんでした。

Linux環境においてWebdriverIOのGet Startedはやや説明不足感があるため、今回の記事では、 Amazon LinuxでWebdriverIOを使ったChromeによるE2Eテスト環境構築の手順を書こうと思います。

前提

項目 バージョン
Amazon Linux 2017.9
Java 1.8
selenium-server-standalone 3.12.0
chromedriver 2.40
WebdriverIO 4.13.0
npm 6.1.0
node 10.5.0

手順

まずは作業用のフォルダを作成

mkdir webdriverio-test && cd webdriverio-test

Javaのアップデート

Selenium Server StandaloneをJavaを動かす際、Amazon Linux 2017.9にデフォルトでインストールされているJavaのバージョンが1.7であるため、アップデートが必要になります。

sudo yum update -y
sudo yum install -y java-1.8.0-openjdk-devel.x86_64
sudo alternatives --config java

sudo alternatives --config java を実行すると、1.7か1.8か選択するように聞かれるので1.8を選択。

ChromeDriverのインストール

ざっくりやることは下記の通りです。

  • ChromeDriverをwgetで持ってきて展開
  • Chromeのバイナリをインストール
  • GConf2をインストール

という流れです。

ChromeDriverをwgetで持ってきて展開

wget https://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip
unzip chromedriver_linux64.zip

sudo ln -s /home/user_name/webdriverio-test/chromedriver /usr/local/bin/chromedriver

Chromeのバイナリをインストール

curl https://intoli.com/install-google-chrome.sh | bash

GConf2をインストール

yum install GConf2で入れようとしても、No package GConf2 available.と怒られるので、リポジトリを追加します。 sudo vim /etc/yum.repos.d/centos.repoで新規ファイルを作成し、下記を打ち込んで保存

[CentOS-base]
name=CentOS-6 - Base
mirrorlist=http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6

#released updates
[CentOS-updates]
name=CentOS-6 - Updates
mirrorlist=http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=updates
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6

#additional packages that may be useful
[CentOS-extras]
name=CentOS-6 - Extras
mirrorlist=http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=extras
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6
sudo rpm --import http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6
sudo yum -y install GConf2

WebdriverIO

nvmによるnodeのインストール

sudo yum install -y git
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
source ~/.bashrc
nvm install node

WebdriverIOのインストール

npm install webdriverio

Selenium Server Standaloneをインストールしバックグラウンド実行

curl -O http://selenium-release.storage.googleapis.com/3.12/selenium-server-standalone-3.12.0.jar
java -jar selenium-server-standalone-3.12.0.jar &

実行

test.jsを作成

WebdriverIO公式のtest.jsをコピペして、'firofox'を'chrome'に変更すれば動くだろうと思いきや、 Chromeの場合は、明示的にheadlessモードを指定しなければ動作しませんでした。 そのため、下記の通りに書き換えます。

var webdriverio = require('webdriverio');
var options = {
  desiredCapabilities: {
    browserName: 'chrome',
    chromeOptions: {
      args: ['--headless', '--disable-gpu'],
    },   
  }
};

webdriverio
  .remote(options)
  .init()
  .url('http://www.google.com')
  .getTitle().then(function(title) {
    console.log('Title was: ' + title);
  })
  .end()
  .catch(function(err) {
    console.log(err);
  }); 
node test.js

Title was: Googleと表示されれば、うまくGetting startedができています!

まとめ

WebdriverIOのスタートガイドは、GUI環境上で動かす分には、Chromeが入っていれば、Seleniumが動かすChromeDriverをインストールするだけで動くのですが、CUI上では、Chromeをバイナリでインストールしたり、GConf2を使うためにリポジトリを追加したり、普段やらないようなことをするので骨が折れました。 また、起動時に--headlessを引数で渡さなければ謎のエラーで動作しないことにも地味にハマりました。

Rails 5.2でActive Storage を使った画像のアップロードを試す

概要

ひょんなことから、画像をアップロードする処理を書くことになりました。
これまで、Railsで画像アップロードといえば、CarrierWavePaperclipを使っておけば良いでしょ〜!くらいに思ってたんですが、せっかくRails5.2を使っているのであれば、Active Storageを使ってみよう!と思ったので、
今回はRails5.2でActive Storageの使った画像のアップロードの方法をまとめます。

なぜActiveStorageなのか

今回のアップロード機能の要件は至極単純でした。

  • あらかじめ、特定のフォルダに画像データが配置されている
  • 上記の画像データを特定のモデルにバッチで添付する
  • アップロードされた画像はオリジナルサイズで閲覧ができる

ただこれだけでした。 サムネイル作成などのファイルの変換、バリデーション、セキュリティなどなど、ガン無視でOKみたいな。 そのため、CarrierWavePaperclipは、やや機能過多感があるため、今回はActive Storageに決定しました。 また、Active Storageの特徴を抜粋すると下記の通りです。

  • DBにファイルのメタ情報と中間テーブルを用意することによって、既存のテーブルにカラムを追加しなくても良い => スキーマの変更が柔軟にできそう
  • レコード:ファイルが1:1にも1:多にもできる
  • 既存モデルにはhas_one_attached :nameと書くだけ。
  • 時間制限付きURLを楽に生成できる

今回の要件に置いてはこの機能があれば十分ですね。

Getting start

railsguides.jp ここ読めばGetting startできるわけですが、コマンドとかをまとめようと思います。

config/storage.ymlの編集

どこに保存すんの?という設定を書きます。

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

amazon:
 service: S3
 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
 secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
 region: us-west-2
 bucket: bucketname

amazonのところの書いた<%= Rails.application.credentials.dig(:aws, :access_key_id) %>については、Rails 5.2からのcredential管理の方法で設定した値を使います。 詳細はこちらの記事を参照してください。

blog.naoshihoshi.com

config/environments/development.rb

config/storage.ymlに設定したawsの設定を反映するために、config/environments/development.rbを修正します。

# Store uploaded files on the local file system (see config/storage.yml for options)
# config.active_storage.service = :local
config.active_storage.service = :amazon

モデルにファイル添付する設定を書く

冒頭で紹介した通り、1レコードにつき1ファイルだけ添付するのであれば、下記設定のみです。

class User < ApplicationRecord
  has_one_attached :avatar
end

ファイルの添付と確認

ファイルの添付と確認はrails consoleで確認します。

$ cd rails_root
$ bin/rails c
user = User.first
user.avatar.attached? #=> false
filepath = File.join(Dir.home, 'filename.jpg')
user.avatar.attach(
  io: File.open(filepath),
  filename: File.basename(filepath),
  content_type: 'image/jpg'
)
user.avatar.attached? #=> true

# アプリケーションを指すblobの永続URLを生成
include(Rails.application.routes.url_helpers)
default_url_options[:host] = 'localhost:3000'

url_for(user.avatar) #=> "http://localhost:3000/rails/active_storage/blobs/09Iiw63f495IkJ09IiwkJ09Iiw6IkJBaH19--7209IiwiZXxsLCJwdXIiOiJibG9iX2lkIn19--726eT09IiwbeaHBEUT09Iiwbe29513f4943f49513f49e4a9c55b4af76f5/filename.jpg"

上記の生成されたURLにアクセスすると画像を閲覧することができます。

まとめ

簡単な設定情報と、モデルにhas_one_attachedという記述を追加するだけでファイルのアップロード機能を作ることができました。さすがRailsですね。 要件によってはリサイズしなければならなかったり、バックグラウンドで処理しなければならなかったりするので、Gemを使うことを検討しなければならないのですが、今回は要件が少なかったため、Active Storageで十分でした。

Rails5.2のcredential管理を試してみた

概要

blog.naoshihoshi.com こちらの機能を開発しているときに、Rails5.2からcredential管理が変更されたことに気づいたので、 今回はRails5.2のcredential管理の方法をざっくり説明しようと思います。

Rails5.2のcredential管理の方法のざっくりとした説明

Ruby on Rails 5.2 リリースノート | Rails ガイド

config/credentials.yml.encファイルが追加され、productionアプリの秘密情報(secret)をここに保存できるようになりました。これによって、外部サービスのあらゆる認証credentialを、config/master.keyファイルまたはRAILS_MASTER_KEY環境変数にあるキーで暗号化した形で直接リポジトリに保存できます。Rails.application.secretsやRails 5.1で導入された暗号化済み秘密情報は、最終的にこれによって置き換えられます。 さらに、Rails 5.2ではcredentialを支えるAPIが用意され、その他の暗号化済み設定/キー/ファイルも簡単に扱えます。 詳しくは、Rails セキュリティガイドを参照してください。

リリースノートのcredential管理の方法を引用しました。

ざっくり言うと、

  • config/master.keyに記述したキー情報でcredential情報を暗号化
  • 暗号化した情報はconfig/credentials.yml.encに書き込む
  • credential情報は普通に人間が読める感じで書ける
  • RailsアプリはRails.application.credentials.dig(:aws, :access_key_id)って感じで簡単に呼べる

って感じです!

使い方の説明

とはいえ、実際使ってみないとよくわからないので、試してみましょう。

$ cd rails_root
$ cat config/master.key
nirejgaks945n3ge9223gsgs
$ cat config/credentials.yml.enc
omtTwkcvAjuPb8CGkx8h2R9EzqMEsEND3cD7he7z7Pd3KKrAas3jrRGGnhDfdYQr

これが初期状態です。 では、続いて、credential情報を登録してみましょう。 登録はrailsコマンドで実行します。

初期状態はsecret_key_baseだけが登録されているのですが、AWSの認証情報を追加してみます。 下記コマンドを実行すると、エディタが開きます。

$ EDITOR=vim bin/rails credentials:edit
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: kgJ3dPHLkTUwNTG3pM9kgJ3dPHLkTUwNTG3pM9k6M8RR4LrRJx7UeKYiebrcqCrh6LNX9GyYXACnqfcFb8Xk6M8RR4LrRJx7UeKYiebrcqCrh6LNX9GyYXACnqfcFb8X

aws:
  access_key_id: AKIAJBVIIRA9LRVFAPOK
  secret_access_key: wNTG3pLkTUX9GyYXM8RR4L7UeKYiebrcFb8Xd

そして、再びconfig/credentials.yml.encを確認すると、情報が追加されている == 値が変更されていることがわかるはずです。

$ cd rails_root
$ cat config/credentials.yml.enc
omtTwkcvAjuPb8CGkx8h2R9EzqMEsEND3cD7he7z7Pd3KKrAas3jrRGGnhDfdYQrwkcvAjuPb8CGkx8hkcvAjuPb8CGGnhDfdYQrwkcvAjuPb82R9EzqMEsEND3MEsEND3c9EzqMEsEND

最後に、Railsから呼び出すことができるか確認します。

$ cd rails_root
$ bin/rails c
irb(main):001:0> Rails.application.credentials.dig(:aws, :access_key_id)
=> "AKIAJBVIIRA9LRVFAPOK"

ばっちりですね!

まとめ

これまでは、.envファイルなどで環境変数を設定したりしていたので、管理が煩雑になっていました。 Rails5.2からは今回紹介したcredential管理の方法を使えば管理が楽になると思います。 今回紹介した通り、使い方もシンプルであるため、おすすめです。

ChromeExtensionのBrowser Actionsで格納したlocalStorageの値をBackground Pagesを使ってContent Scriptsからアクセスする方法

概要

Chrome ExtensionのBrowser Actionsで認証リクエストし、レスポンスのJWTをlocalStorageに格納する処理を作っていました。
その後、さらに拡張してContent ScriptsでJWTを使って他のAPIリクエストをするときに、「あれ?localStorageのスコープ違くね?\(^o^)/」となったので、
Background Pagesを使ってContent ScriptsからlocalStorageにアクセスできるようにしました。
今回はその方法をサンプルコードを用いて紹介します。

また、今回は下記のような色々な前提をすっ飛ばして説明します(!)

  • localStorage
  • Chrome Extensionのスクリプトの種類など
  • パッケージ化されていない拡張機能の確認方法

処理の方針

概要にも書きましたが処理としてこんな感じです。

  1. Browser ActionsでlocalStorageに何かしらの値を格納
  2. 1.で格納した値をBackground Pagesから取得できるようにする
  3. Content ScriptsからBackground Pagesの処理を呼べるようにする

manifest.json

まずはmanifest.jsonを書いていきます。 方針の通り、browser_Actions, content_scripts, backgroundを定義していきます。

{
  ...
  "browser_Actions": {
    "default_popup": "index.html",
  },
  "permissions": ["tabs"],
  "content_scripts": [
    {
      "matches": [
        "http://*/*",
        "https://*/*"
      ],
      "js": [
        "content_script.js"
      ]
    }
  ],
  "background": {
    "scripts": [
      "background.js"
    ]
  },
  ...
}

Browser Actions

ここはサラッといきます! 普通にlocalStorageにsetするだけです。

localStorage.setItem("key","value");

Background Pages

今回のポイント1つめです。 Background Pagesを作る際に考えなければならないのは、Content Scriptsから呼び出せるようにしなければならないことです。 そこで活躍するのがChrome APIのchrome.runtime.onMessage.addListenerです。 Chrome Extensionではスクリプト同士を連携させるためにメッセージを使ってやりとりします。 Background PagesではContent Scriptsから呼び出される側なので、受け口(Listener)を作ります。

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  switch (request.method) {
    case 'getItem':
      sendResponse({data: localStorage.getItem(request.key)});
      break;
    default:
      console.log('no method');
      break;
  }
});

method名と任意のkey名を受け取れるようにして、中身を見てよしなに処理を変えるといった感じです。

Content Scripts

Content ScriptsではBackground Pagesに定義した処理を呼びます。 Background Pagesではchrome.runtime.onMessage.addListenerを使ってメッセージの受け取り側の定義をしましたが、 Content Scriptsでは、chrome.runtime.sendMessageを使って、メッセージの送信側を定義します。

document.addEventListener('click', function(e) {
  chrome.runtime.sendMessage({method: 'getItem', key: "key"}, function (response) {
    if (response.data) {
      console.log(response.data);
    }
  });
}, false);

画面の適当なところクリックしたら、ログに取得した値を表示するといった内容です。

まとめ

今回はBrowser Actionsで格納したlocalStorageの値をContent ScriptsがBackground Pagesを経由して取得する処理について説明しました。
localStorageのスコープの違いは、意識していないとハマってしまうポイントです。
Content ScriptsとBackground Pagesをメッセージ通信させることで、Chrome Extensionの異なるスクリプト同士を連携させることができます。

小さいスクリプトでサクッと確認することができたので、良い学びになりました!

Railsでpreflightリクエストを処理し、no route matches optionsを回避する方法

概要

前回の記事ではRailsを使って、APIの受け側を作りました。
ただ、このままだとJSでフロントエンドからAPIに対してリクエストした場合、preflightリクエストが飛ぶので、No route matches [OPTIONS]エラーが返ってしまい、正常終了できません。
今回は、Railsでpreflightリクエストを処理し、no route matches optionsを回避する方法を書きます。

処理の方針

fetch("http://localhost:3000/auth_user", {
  method: 'POST',
  body:  JSON.stringify({ emai: "emai@email.com", password: "********"})
}).then(function(response) {
  return response.json();
}).then(function(json) {
  ...
});

こんな感じでフロントからリクエストをすることを想定します。
fetchでリクエストする場合、クロスドメインアクセスが可能か確認するするため、OPTIONSリクエストを飛ばして、それが200になってから本来のリクエストを飛ばします。
また、preflightリクエストが成功にするには、2つ条件があります。

  • OPTIONSをルーティングできること
  • response.headersに適切なキーとバリューが設定されていること

今回は、上記2つの条件を満たすための処理を書くことを目標にします。

OPTIONSをルーティングできること

乱暴ではありますが、いかなるpathのリクエストに対してもOPTIONSリクエストであれば、response.headersの設定を行えるようにします。 config/routes.rbに下記を追加しましょう。

match '*path' => 'options_request#preflight', via: :options

今回はoptions_request_controller.rbを作成して、そこで処理をすることにします。

response.headersに適切なキーとバリューが設定されていること

ルーティングファイルに従って、options_request_controller.rbを作成します。 また、response.headersに適切なキーとバリューが設定しなければならないので、このように処理をします。

class OptionsRequestController < ApplicationController
  ACCESS_CONTROL_ALLOW_METHODS = %w[GET OPTIONS PUT DELETE POST].freeze
  ACCESS_CONTROL_ALLOW_HEADERS = %w[Accept Origin Content-Type Authorization].freeze
  ACCESS_CONTROL_MAX_AGE = 86_400

  protect_from_forgery except: :preflight

  before_action :set_preflight_headers!, only: [:preflight]

  def preflight
    response.headers['Access-Control-Max-Age'] = ACCESS_CONTROL_MAX_AGE
    response.headers['Access-Control-Allow-Headers'] = ACCESS_CONTROL_ALLOW_HEADERS.join(',')
    response.headers['Access-Control-Allow-Methods'] = ACCESS_CONTROL_ALLOW_METHODS.join(',')
    response.headers['Access-Control-Allow-Origin'] = '*'
    head :ok
  end
end

こちらの処理の通り、response.headersの中にいくつかキーとバリューを設定しています。
レスポンスヘッダーの詳細説明に関しては、こちらの記事に譲るとして、やっていることをざっくり説明するとこのようになります

キー バリュー 意味
Access-Control-Max-Age 86400 preflightリクエストの結果を24時間キャッシュ
Access-Control-Allow-Headers Accept,Origin,Content-Type,Authorization リクエスト時にどのヘッダーが使用可能か明示
Access-Control-Allow-Methods GET,OPTIONS,PUT,DELETE,POST リソースのアクセス時に許容するメソッドを明示
Access-Control-Allow-Origin * 許容できるアクセス元のURLを明示

まとめ

今回、とりあえずハマりを抜け出すために、簡易な処理を書きましたが、要点としては下記2点です。

  • OPTIONSリクエストを受け付けるようにする
  • 受け付けたOPTIONSリクエストのレスポンスにAccess-Control-Allow-ほげほげをつける

SPAでAPI連携をするときにハマりポイントではありますし、何回作っても毎度ハマるので参考にしていただければと思います

Rails 5.2でJWTとdeviseを使った認証の仕組みを作る

概要

最近フロントエンドからAPIを叩く実装における認証の仕組みをどうするか考えていました。
以前、AWS Cognitoを使った認証仕組みを作ったことはあったのですが、Railsの場合はどのようにJWTを扱うか知りたかったので、作ってみました。

今回は、タイトルの通りRails 5.2でJWTとdeviseを使った認証の仕組みについて解説します。*1

想定しているケース

想定しているケースはシンプルです。
ユーザーの動き的には、こんなことをすることを想定しています。

  1. ユーザーがブラウザからメール / パスワードを入力してユーザー登録
  2. ユーザーがAPIでデータのやりとりをするために、CUIでメール / パスワードを使ってトークンを取得
  3. 1で取得したトークンをヘッダーに入れて、リクエストをすると、APIが叩けるようになる。

この記事では、主に2, 3.の実装について、説明します。

調査したこと

想定しているケースの「ユーザーがブラウザからメール/パスワードを入力してユーザー登録」はdeviseに任せようと思います。 トークンの発行と検証は、よくわからなかったので、調査したこととしてて、2つありました

  • JWTを扱うための仕組みがdeviseにあるか?
  • なければ、それを補うためのgemがあるのか?

JWTを扱うための仕組みがdeviseにあるか?

まだ未実装でした\(^o^)/

なければ、それを補うためのgemがあるのか?

ありました!!

Starの数を見ると、ruby-jwtが圧倒的だったのと、READMEを見ても、使い方がシンプルでわかりやすかったので、採用しました。

ユーザーがブラウザからメール / パスワードを入力してユーザー登録

まずは、ブラウザからアクセスできるようにしたり、ユーザー登録をするための下準備をします。

# app/controllers/top_controller.rb
class TopController < ApplicationController
  def index
  end
end
# config/routes.rb
Rails.application.routes.draw do
  root 'top#index'
end

つづいて、Gemを追加

# Gemfile
...
gem 'devise'
gem 'jwt'
...

その後こちらのコマンドを実行

bundle install
rails g devise:install
rails g devise User
rake db:migrate

トークンの取得、検証処理の追加

ここから、一気にトークン発行、検証の実装をしていきます。

lib/json_web_token.rbを新規作成します。

# lib/json_web_token.rb
class JsonWebToken
  class << self
    def encode(payload)
      JWT.encode(payload, Rails.application.credentials.config[:secret_key_base])
    end

    def decode(token)
      HashWithIndifferentAccess.new(
        JWT.decode(
          token,
          Rails.application.credentials.config[:secret_key_base]
        )[0]
      )
    rescue
      nil
    end
  end
end

JWTの秘密鍵は、Railsのsecret_key_baseを使いましょう。 *2

次に、上記のクラスをいちいちrequireをするのがめんどくさいので、config/initializers/jwt.rbを作って、Rails起動時に読み込まれるようにします。

# config/initializers/jwt.rb
require 'json_web_token'

続いて、ApplicationControllerでトークンのやりとりをするための処理を追加

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

  protected

  def authenticate_request!
    unless user_id_in_token?
      render json: { errors: ['Not Authenticated'] }, status: :unauthorized
      return
    end
    @current_user = User.find(auth_token[:user_id])
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end

  private

  def http_token
    @http_token ||= if request.headers['Authorization'].present?
                      request.headers['Authorization'].split(' ').last
                    end
  end

  def auth_token
    @auth_token ||= JsonWebToken.decode(http_token)
  end

  def user_id_in_token?
    http_token && auth_token && auth_token[:user_id].to_i
  end
end

最後に、トークンを作るためのAPIの受け口を作っていきます。

# app/controllers/authentication_controller.rb
class AuthenticationController < ApplicationController
  protect_from_forgery except: :authenticate_user
  def authenticate_user
    user = User.find_for_database_authentication(email: params[:email])
    if user.valid_password?(params[:password])
      render json: payload(user)
    else
      render json: {errors: ['Invalid Username/Password']}, status: :unauthorized
    end
  end

  private

  def payload(user)
    return nil unless user and user.id
    {
      auth_token: JsonWebToken.encode({user_id: user.id, exp: (Time.now + 2.week).to_i}),
      user: {id: user.id, email: user.email}
    }
  end

protect_from_forgery except: :authenticate_userを設定しないとAPIを叩いた時に、CORSエラーとなるため、設定しておきます。
※今回の実装だと、JWTは一度発行すると2週間は失効できなくなるので、payloadのexpをよしなに変更するか、別実装は考えておいた方が良いと思います。

config/routes.rbに下記を追加。

# config/routes.rb
post 'auth_user' => 'authentication#authenticate_user'

最後に、top_controllerにこれまで作ってきたactionをbefore_actionとして設定します。

# app/controllers/top_controller.rb
class TopController < ApplicationController
  before_action :authenticate_request!
  def index
    render json: {'logged_in' => true}
  end
end

これで実装完了です!

動作確認

実装が終わったので、意図した実装になっているか、確認します。

ブラウザからユーザー登録していくのはめんどくさいので、rails cでやっちゃいましょう。

$ rails c
User.create(email:'test@test.com', password:'changeme', password_confirmation:'changeme')

これで下準備は完了です。
rails sで起動して、curl http://localhost:3000/を実行してみましょう。

レスポンスとして{"errors":["Not Authenticated"]}が返ってくればOKです。

トークンの取得

curl -X POST -d email="test@test.com" -d password="changeme" http://localhost:3000/auth_user こちらを実行すると、レスポンスとしてトークンが返ってきます。

{"auth_token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1MjYxOTA1MDB9.ABVCQGdzF3u2XcAp66vXZxeUy2dhsCuxsg88NsEdoFs","user":{"id":1,"email":"test@test.com"}}

トークンを使ってAPI実行

返ってきたトークンを使えば、認証が通るようになっているはずです。やってみましょう。

curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1MjYxOTA1MDB9.ABVCQGdzF3u2XcAp66vXZxeUy2dhsCuxsg88NsEdoFs" http://localhost:3000

{"logged_in":true}というjsonが返って来れば成功です!

まとめ

基本の形はこれで十分なのではないかと思います。

システムによって検討しなければならない点としては、先述の通り、トークンの失効するタイミングについてです。 この実装では2週間待たなければトークンは失効されず、ずっと有効なままです。そのため、仮に失効しなければならないことがあった場合に、ほぼ対応不能となるので、認証時の処理を少し変える必要があると思います。

とはいえ、RailsでJWTを簡単に扱えるgemがあったので、JWTを使った認証の仕組みをサクッと作ることができました。 deviseが入っているシステムに対しての連携もうまくいったので、良い学びになりました。

*1:Rails, JWT, deviseの説明は省きます

*2:Rails 5.2から秘密鍵周りが変わったので、5.1以下を使っている方は書き方が多少異なります。 おそらくRails.application.secrets.secret_key_baseとなるはず。

Redashのバージョンアップで消えたデータソースを復活させる方法

Redashのバージョン2系から3系にupgradeした際、DataSourceのプルダウンから色々なものが消えてしまいました(!)

  • BigQuery
  • Athena
  • Google Analytics
  • Google Spreadsheet

業務でも使っているデータソースだったので、これが使えなくなるとなかなか厳しいです。
今回は、Redashのバージョンアップで消えてしまったこれらのデータソースを復活させる方法を書きます。

続きを読む

AWS Database Migration Serviceを使ったデータベース統合を試してみました

概要

1月20, 21日と、会社の開発合宿に行ってきました。 取り組んだ内容はタイトルの通り、AWS Database Migration Service(以下DMS)を使ったデータベース統合です。

解決したかった問題

PIXTAではマイクロサービスアーキテクチャを採用しています。
また、サービスは大きく4つに分かれており、それぞれ独立したDBを持っています。

サービスの運用をしていると、しばしばデータ抽出依頼がくるのですが、その依頼内容の中には複数のDB間をまたいだテーブル結合が必要になってくる依頼もあります。
「Aサービスが持つDBのテーブルの条件 かつ Bサービスが持つDBのテーブルの条件を満たしたもののうち、CサービスのDBのテーブルの内容を抽出」といった感じです。

これまで、このようなデータ抽出依頼は下記のようなことをやっていました。

  1. CサービスのDBの値が取得できるように、外部キーをBサービスのDBから取得するクエリ
  2. そのBサービスの値を取得するための外部キーをAサービスのDBから取得するためのクエリ
  3. 最後にCサービスのDBに対しての本当に欲しかった値のクエリ

本当は、クエリ1つで済むはずなのに・・・。

そこで、今回の開発合宿はDMSを使ったデータベース統合に取り組むことにしました。

AWS Database Migration Serviceとは

簡単に言うとDB移行サービスです。
オンプレ / AWS内問わず、どちらかがAWS内にあれば、移行が可能です。
また、Oracle から AWS Auroraなど、異なるDBプラットフォーム間の移行もDMSがうまいことやってくれます。
さらに単純に移行だけではなく、MySQLとMariaDBとか、2DBを1つのDBに異種交配することも可能ですし、移行元のデータの差分更新を検知し、反映してくれます。ダウンタイムもありません。

f:id:watasihasitujidesu:20180122131648p:plain

AWS Database Migration Serviceでデータベースを統合するまでの手順

では具体的に処理の手順を説明していきます。 処理自体は3ステップなので簡単です。

  1. Replication Instanceを作成
  2. SourceとなるDBの設定と移行先のDBの設定
  3. データ移行タスクの設定

1. Replication Instanceを作成

DMSの本丸と言いますか、タスクを実行するために必要なインスタンスを作成します。

DMS > Replication Instances > Create replication instance

f:id:watasihasitujidesu:20180121121944p:plain

次に、インスタンスの情報を諸々設定していきます。 VPCに入れることが前提なので、ネットワーク関連でややハマるかもしれません。

f:id:watasihasitujidesu:20180121122141p:plain

今回、Production相当のRDSをターゲットに移行をしたのですが、インスタンスサイズに関してはあまりリソースは必要としていないようでした。 t2.smallでも十分でした。

2. SourceとなるDBの設定と移行先のDBの設定

続いて、Replication Instanceで処理する移行対象のDBの設定します。 データ移行元となるSourceと、データ移行先となるTargetの2つ設定が必要です。

Source Databaseの設定

DMS > Endpoints > Create endpointを押下します。

f:id:watasihasitujidesu:20180121122848p:plain

画像の赤枠でも囲ってあるServernameはホストを指定します。*1

次に、導通確認をします。

f:id:watasihasitujidesu:20180121123144p:plain

Run testが通れば最低限、Replication InstanceからDBに接続ができています。

Target Databaseの設定

Target Databaseも基本はSourceDBと同じです。
自分が勘違いしていた点は、TargetDBは、DMSの方で用意してくれるものかと思っていたのですが、DMSの挙動は、TargetDBに対して、SourceDBをコピーする動きになるので、TargetDBはあらかじめ用意する必要がありました。

3. データ移行タスクの設定

DMS > Tasks > Create taskを押下します。

f:id:watasihasitujidesu:20180121135011p:plain

基本的には、SourceDBとTargetDBを設定して、Replicationの設定を行うだけです。

Migration Typeは3種類あり、それぞれ、下記のような挙動となります。

Migration Type 用途
Migrate existing data 現在のSourceDBの情報のみをReplicationする
Migrate existing data and replicate ongoing changes 現在のSourceDBの情報と、SourceDBに変更が加わった場合に差分更新を行う
Replicate data changes only SourceDBに変更が加わった場合にその変わったデータのみを追加する

また、後続の設定項目である、Task Settings の Enable loggingにチェックを行うとDMSで行う処理のログがCloudWatchに吐かれます。
ログは、通常処理の状況ではなく、Errorになった場合の情報も吐かれるので、チェックをしておくのをオススメします。

エラー状況はStatusやTables errorsの状況でわかります。 下記の図はエラーが76件出ているので、ページ下部のLogsからCloudWatchのリンクを開き、確認するとログを見ることができるはずです。

f:id:watasihasitujidesu:20180122131829p:plain

タスクは実行されれば、テーブルごとに1万件ずつinsertが走っていきます。 また、最大8並列で実行されます*2

何事もなければ、TaskのStatusがLoad compliteとなり、レプリケーションは完了です。

まとめ

これまで、分割されたDBからデータ抽出をするのに、何回もクエリを実行したり、DBを統合するのにスクリプトなどを書く必要がありましたが、AWS DMSを使えば、マネジメントコンソール上からの設定で手軽にDB統合することができました。 また、データ統合だけではなく、その逆の統合されたDBを再度分割することも可能です。

DMSは当初、オンプレのDBを移行するだけのものだと思っていましたが、このような使い方ができるには驚きました。

ただ、200GB以上のDBをレプリケーションしようとした場合、移行にかかる時間が数時間かかっていたので、速度改善の余地は十分にありそうです。

*1:当初AWSのサービスだし、RDSのインスタンス名の入力かと思いきやそうではありませんでした

*2:TargetDBがRedshift以外の場合

macOS Sierraでssh時にパスフレーズを聞かれないようにする方法

概要

PCを変えてsshしたときにいつもパスフレーズを求められる => 設定する
って作業をしており、毎回どうやんだっけ?ってなるので、自分のためにメモ。

手順

1. 秘密鍵の登録

ssh-add -K ~/.ssh/id_rsa

2. macOS標準アプリのAutomatorの登録

2-1. Automatorを開く
f:id:watasihasitujidesu:20180105134357p:plain

2-2. アプリケーションを選択
f:id:watasihasitujidesu:20180105134428p:plain

2-3. シェルスクリプトを実行を選択し、コマンドを打ち込み、保存 f:id:watasihasitujidesu:20180105134555p:plain
コマンドは下記の通り。 ssh-add -A

3. ログイン時にAutomatorが実行されるように設定

3-1. システム環境設定 > ユーザーとグループを選択 f:id:watasihasitujidesu:20180105134710p:plain

3-2. + ボタンを押下し、2で作ったスクリプトを選択 f:id:watasihasitujidesu:20180105134846p:plain

まとめ

PC起動時にssh-key -Aが実行されるように、Automatorでスクリプトを作成し、そのスクリプトをログイン項目として設定。
ssh-key -Aをした場合に、ssh agentに秘密鍵が登録されていなければ、パスフレーズを聞かれてしまうので、事前にssh-add -K ~/.ssh/id_rsaで登録をした
という感じ。

crontabで使用するエディタを変更する方法

概要

新しくサーバーを立てた際、cron設定しようと思い、crontab -eをした時に、
あ、あれ、、何このエディタ....
って感じになり、エディタの設定を毎回変更するも、そのコマンド自体を覚えていないという由々しき状態になるので、メモとして残しておく

コマンド

$ select-editor

こんだけ!!!!!

そしたら多分、

Select an editor. To change later, run 'select-editor'.
1. /bin/ed
2. /bin/nano <---- easiest
3. /usr/bin/vim.basic
4. /usr/bin/vim.tiny

Choose 1-4 [2]:

こうなるから、いつも使ってるvimを選択*1

*1:easiestってなんやねん!!!