アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Webど素人のSierがWebエンジニアになんとかなれた話

Webエンジニアのnakamuraです。 今回はタイトルにもあります通りWebのことなんてHTMLぐらいしかしらなかったメーカー系のSierだった僕がどのような経緯でWebエンジニアになったかを紹介したいと思います。 これから、Webエンジニアになりたいと思っている人たちに多少なりとも参考になる部分があれば幸いです。

「システムエンジニアだった頃の話」

今から20年くらい前に大学の情報系学科を卒業した僕は、新卒でメーカー系のIT企業に就職しました。 システムエンジニアとして、いくつかのプロジェクトで開発を担当し、働く環境にも、仕事に対するやりがいにも、特に不満はなかったので、当時の僕はこのまま経験を積んでこの会社にずっといるんだろうと本気で考えていました。 ところが、4年目くらいになった頃に、開発はもういいから、PMの方に進んでと言われ、まだまだ開発をやっていたかった僕はこのあと数年後に退職し、英語が使えるようになれば転職にも困らないかなくらいの軽い気持ちで8ヶ月の語学留学を決意します。

「渡英した頃の話」

渡英した当時(2007年くらい)は、ポンドがめちゃくちゃ高かったので、みるみる貯金が減っていきました。 なので、語学学校の空き時間でできるオンラインショップの梱包作業員の募集に応募することにしました。

いざ、面接に望むと僕の履歴書を見たイギリス人が、「ちょうどPHPのECサイトをRailsに移行しようと思ってるんだけど、エンジニアやってたんだったら、やってみない?やるならVISA取るから。」と。

もともと、1つのプロダクトを、全体を通して関わってみたい思っていたので、Webに関する知識は皆無でしたが、一緒に勉強しながらでいいからということだったので、やってみることにしました。 この頃の僕は、Rubyを少し知っていたくらいでRailsのことなんてまったく知りませんでした。

で、当時、渡された1冊の本がagile-web-development-with-railsです。 Railsのことを知らなかった僕は、Railsの読みやすさに衝撃を受けます。 そこから、とりあえず、チュートリアルに従ってサンプルアプリケーションを作り、わからないところはあとで振り返るということにして、どんどん先に進みました。 半年くらいして、なんとなく仕組みがわかるようになってから、Railscastsなんかも見るようにして、アプリケーションのアップデートを繰り返していました。

それから、自力で構築したブログサイトをリリースし、その後に、同僚のお手伝いで日本料理教室の予約サイトを2、3ヶ月かけて構築しました。

同時に、ECサイトの開発も同僚と2人で進めていたのですが、当時のRailsに関する僕らの知識ではPHPで実現出来ていたことがRailsだとなかなかうまく実装することができず悪戦苦闘していました。

そして、しばらくすると、同僚のイギリス人が転職活動を始め、数ヶ月後に転職することになり、メインだったECサイトの移行を1人でやらざるを得なくなりました。

この頃は、ロンドンの物価の高さに加え、近所の子供たちにマフィンを投げつけられたり、ロンドンの天気に憂鬱になったりと、今すぐにでも日本へ帰国したい気持ちでいっぱいでした。

ただ、ECサイトもほぼほぼ完成に近づいていたので、リリースを諦めきれず、なんとかリリースにこぎつけることができました。

以下が、その時に利用していた主なサービスになります。

・サーバー www.brightbox.com

・検索エンジン sphinxsearch.com

・メール配信サーバー www.mandrill.com

・カード決済 www.sagepay.co.uk

ECサイトを無事リリースしたあとは、様々な機能強化を行いながら、5年くらいで当初から目標にしていた売り上げ額に到達することができました。 その間、ECサイトの他にも、いくつかのアプリケーションをリリースしていましたが、自分は果たして日本でもう一度働けるのか?ということを思い始め、帰国を決意しました。 8ヶ月の滞在のつもりが、最終的に8年ちょっとの滞在になってました。

「帰国してからの話」

帰国後は、すぐにGreenとPaizaを使って転職活動を始めましたが、久しぶりの地元の居心地の良さに感激し、地元の市役所で働くことになります。 結果的に2年半ほど働くのですが、やはり、もう一度、プロダクト開発に携わってみたいという希望があったので、またまた転職を決意します。 その間、やっていたことと言えば、Paizaでひたすらrubyの問題を解いたり、EveryDay RailsでRspecを復習したり、Ansibleを勉強したり、新しいバージョンのRails本を読んだりしながら、小さいアプリケーションの作成を続けていました。

「最後に」

なんとか日本でWebエンジニアとして採用され、1人で開発していたころの違いに衝撃を受けている毎日ですが、1人の時には出来ていなかったコードレビュー、使用していなかったプロジェクト管理ツール、インテグレーションツールの有用性を思い知らされるなど、充実した日々を過ごせているように思います。

僕がWebエンジニアになるきっかけになった10年前と今では、エンジニアに求められる知識や技術も幅広くなっていると思いますが、まずは小さいアプリケーションを作ることからはじめてみてはいかがでしょうか?

アクトインディでは、一緒に働いてくれるエンジニアを募集しております。 興味のある方はぜひお越しください!

メディア素人のWebディレクターがお出かけメディアを3年やって思ったこと3つ

いこーよ」「いこレポ」のWebディレクターをやってます、tsujimotoです。

「いこーよ」の姉妹サイトとして、2017年7月末にローンチしたお出かけ記事専門サイト「いこレポ」が、立ち上げ10ヶ月(2018年5月)で通算利用者100万ユーザー、8月には年間利用者200万ユーザーまで順調にグロースしております。

また、8月末まで実施していた1周年キャンペーンも無事に終了してひと段落したのもあり、晴れて開発者ブログに初参戦してみました!

今でこそ立ち上げたメディアを「順調にグロースできた」と(多少は)言える状態になりましたが、私自身アクトインディでお出かけメディアに携わる前は、7年ほど書籍やエンタメ系ECサイトのWebディレクターをやっており、メディアに関してははっきり言ってド素人状態でした。

そんな私がお出かけメディアのWebディレクターを3年やって思ったこと、良かったこと、やってみたことを3つに絞ってまとめました。

  • 1.〇ヶ月待てば自然流入は増える?(我慢のSEO)
  • 2.やっぱり速いは正義!(turbolinksと仲良くなったら)
  • 3.コンバージョンポイントが見えづらい(貢献度可視化でモチベーションアップ)
  • 最後に

1.〇ヶ月待てば自然流入は増える?(我慢のSEO)

ここでは実際に私が担当したコンテンツSEOの事例を2つ挙げたいと思います。

【事例1】いこーよ年齢別リニューアル(2015年11月)

元々は0歳~9歳の各年齢に応じた1枚ペラの特集ページだったものを大幅改修して、従来の年齢別の特集配下に年齢 × 地域・都道府県のお出かけ情報まとめコンテンツを追加しました。

iko-yo.net

結果

⇒⇒⇒ リニューアル後の自然検索流入数が、2ヶ月で4倍、1年で10倍、2年で30倍、3年で40倍!

年齢×地域・都道府県一覧の推移(月別)
年齢×地域・都道府県一覧の推移(月別)

雑感

リニューアルの目玉の年齢 × 地域・都道府県ページは、ローンチ2~3ヶ月でいったん上昇曲線を描くものの、その後大きく失速しました。Googleのindexが安定してないせいなのか、最初の6ヶ月は上がったり下がったりを繰り返していました。 流れが変わったのはリニューアルして8ヶ月が過ぎたころ。横ばいの月もあるものの、今までのように大きく落ち込む月はなくなり、15ヶ月目には確かな成長を実感できるまでになり、最近1年はリニューアル前の実に"40倍"で推移しています。 (因果関係ははっきりしませんが、社内では17ヶ月目にhttps対応したことによるブースト説あり?)

学んだこと

最初の半年はとにかく我慢。1年半くらい我慢できれば結果はついてくる! いこーよのお出かけ一覧とのカニバリが懸念されましたが、Google側でいい感じにLP最適してくれているようです。40倍成長でそれなりのトラフィックを稼げるようになったことで、鈴鹿サーキットさんに年契で広告出稿いただけるまでになりました!(感謝)

【事例2】いこレポ(2017年7月)

「いこーよ」の姉妹サイトとして満を持してオープンしたものの、初月は夏休み中にもかかわらずリアルタイムアクセス1桁がざらという、閑古鳥が鳴いている状態でしたが・・・。

report.iko-yo.net

結果

⇒⇒⇒ 立ち上げ半年で1.8倍、1年で8倍に急成長!

いこレポ自然検索セッション(月別)
いこレポ自然検索セッション(月別)

雑感

季節要因でお出かけメディアには試練の冬場を迎えた逆境があれども、最初の5ヶ月は突発的なアクセス増加が数回あった以外は、ほぼ無風の伸び悩み・・・。 厳しい状況が続いて心が折れかける中、一筋の光が見えたのは6ヶ月目で迎えた春の訪れでした。なかなか数字が付いてこない中でも編集部がトライ&エラーを重ねてくれたことで、春休み期間にいくつか結果が伴う記事も出てきて、「強み」といえる切り口も生まれました。

地道な努力で得た経験と強みを生かすことで、2018年8月には前年8倍と一気にグロースを実感する規模に成長して、1周年を迎えることができました!

いこレポ1周年記念イラスト
いこレポ1周年記念イラスト

学んだこと

我慢、我慢、我慢。自然流入が伸び悩む中でも、編集部は方針を変えずにトライ&エラーを重ねてくれたこと、制作側も腐らずにサイト改善を進めたことで、直帰率も前年より5%ほど改善しました。

まとめ

一般的にSEOは「すぐに効果は出ない」「3ヶ月、半年かかる」などと言われますが、キーワードによってケースバイケースなところもあるし、実体験が伴わないと腹落ちしづらい部分も多いと思います。私自身、前職ではDVDやCD、書籍のSEO対策をやっていましたが、旬が短い商材のテールワードで十分な効果を図ることは難しく、じっくり腰を据えてGoogle先生(自然検索)と向き合いたいと思っていた中で、実際に新規コンテンツやメディア立ち上げを経験させてもらうことができ、実体験で語れるようになりました!

2.やっぱり速いは正義!(turbolinksと仲良くなったら)

「いこレポ」は「いこーよ」と同様、Ruby on Railsで開発されていますが、「いこーよ」では未利用のturbolinks(ページ遷移をAjaxに置き換え、JavaScriptやCSSのパースを省略することでレンダリングが高速化するgem)を実験的に使っています。

Rubyエンジニアには広く知られているそうですが、このturbolinksはレンダリングの高速化とトレードオフで、JavaScriptのイベントが発火しなかったり、Google Optimizeと併用した際に表示不具合を起こしてしまったり、なかなか厄介な存在です。

ただ、やっぱりレンダリング高速化の恩恵は大きく、ネイティブアプリと見間違えるくらいの爆速ぶりを見せてくれるので、できることなら活用したいと思う心理もわかります。(悩ましい・・・)

じゃあturbolinks外したら数字的なインパクトってどのぐらいだろ? ということで、思い切って外してみました。気になる結果は・・・。

結果

⇒⇒⇒ turbolinks外したら回遊0.7落ちた~( ;∀;)

まとめ

やっぱり速いは正義でした・・・! 体感では若干遅くなったかなぐらいでしたが、結果は如実に表れていて、回遊落ちとともに直帰も3%程度悪化しちゃいました。この時は2日間turbolinksを外しましたが、やっぱり今の「いこレポ」には必要ということですぐに戻して現在に至ります。そのためイベント発火の関係で読了率が取れなかったり、広告impに影響出たりと問題は抱えていますが、うまく付き合う方法を模索中です。

f:id:t_tsujimoto:20180919160924j:plain

SEO的な観点でGoogleのスピードアップデートやモバイルファーストインデックス(MFI)とturbolinksの関係性は未知な部分も多いので、興味のあるエンジニア、ディレクターはぜひ色々実験してみると良いと思います!※あくまで自己責任でお願いしますm(__)m

3.コンバージョンポイントが見えづらい(貢献度可視化でモチベーションアップ)

WebメディアとECとの決定的な違いであり、自分の中でモチベーションが上がらない(+モヤモヤを感じていた)原因になっていたのは、Webメディアでは「商品購入」ような明確なコンバージョンポイントが設定できない、つまりユーザーがどんなアクションを取れば成果になりGoalとみなすか可視化しづらい点でした。

やってみたこと

そこで「いこレポ」でやってみたのは、記事の中でお出かけ施設やイベントを紹介する枠から「いこーよ」へ遷移した数を「仮想コンバージョンポイント」に設定することでした。実際にGoogleアナリティクスの目標に「いこーよ」へ遷移した数を設定しました。

"仮想コンバージョンポイント"
"仮想コンバージョンポイント"

結果

⇒⇒⇒ 来訪セッションの約10%が、この枠をクリックして「いこーよ」の施設ページに遷移し、「仮想コンバージョン」していた!

まとめ

一般的にメディアの主要KPIは直帰や回遊、滞在時間などで図ることになりますが、必ずしも直帰や回遊だけでページの良し悪しを判断することはできません。

特に「いこレポ」の場合、turbolinksの影響で読了率が取れないため、必然的に直帰・回遊を重要視することにはなるものの、例えば外部サイト扱いの「いこーよ」へ遷移した段階で離脱扱いになってしまい、ただ直帰や回遊を見ていただけでは記事がユーザーニーズに応えられたのか応えられていないのか判断することは困難でした。

今回約10%が「いこーよ」に遷移していたことがわかったことで、(ざっくりですが)アナリティクス上の直帰率より実態は10%低く、回遊に換算すると+0.1程度の効果になることがわかりました。

記事単位で見ても傾向が違っていて、例えば直帰率が平均より高い記事だけど実は20%が「仮想コンバージョン」していたなど、直帰や回遊、滞在時間からは見えてこない「本当のユーザー行動」が可視化できたのが大きな収穫でした。数字が可視化できたことで、貢献度もより具体的になり記事を作っている編集担当のモチベーションアップに繋がりました! f:id:t_tsujimoto:20180919170128j:plain

最後に

SEOもサイト改善も実行してすぐに大きな成果は望めません。せっかく作ったコンテンツが半年~1年経っても伸び悩みでいてお困りのWebディレクター・Web担当者さんは、ぜひ現状の施策を振り返り、いまは「待ち」の時期なのかそうでないのか冷静に分析してみてください。そして、いまできる範囲でトライ&エラーを繰り返してみることをオススメします。自分を信じてやり切ることができれば結果は必ずついてきます! また経営層や役職者の皆さまは、温かい目で自社のWebディレクターの施策を信じて応援いただければ幸いです。(切に…)

アクトインディでは、こんな感じで「いこーよ」「いこレポ」といったお出かけメディアと向き合い、試行錯誤しながら一緒に成長させてくれるエンジニアや新しい仲間を募集しています。思い切ってトライ&エラーするにはいい環境だと思います!

★いこレポの立ち上げについては、エンジニアmorishitaの投稿もあわせて読んでみてください★

tech.actindi.net

ActiveRecord::Base#reload はインスタンス変数をクリアしない

komatsu(@nomnel)です。
小ネタですがタイトルの事象でハマってしまったので記事にします。

どういうこと?

下のコードで説明します。

irb(main):001:0> foo = Foo.first
=> #<Foo id: 1, name: "foo", created_at: "2018-08-31 06:25:39", updated_at: "2018-08-31 06:25:39">
irb(main):002:0> foo.name = "#{foo.name}_bar"
=> "foo_bar"
irb(main):003:0> foo.instance_variable_get(:@bar)
=> nil
irb(main):004:0> foo.instance_variable_set(:@bar, 1)
=> 1
irb(main):005:0> foo.instance_variable_get(:@bar)
=> 1
irb(main):006:0> foo.reload
=> #<Foo id: 1, name: "foo", created_at: "2018-08-31 06:25:39", updated_at: "2018-08-31 06:25:39">
irb(main):007:0> foo.name
=> "foo"
irb(main):008:0> foo.instance_variable_get(:@bar)
=> 1

irb(main):002 で 変更した foo.namefoo.reload で変更が無かったことになるのに、 irb(main):004 でセットしたインスタンス変数 @barfoo.reload 後もそのままの値を持ち続けるので注意しましょう…ということです。

今回は既存コードを下のようにインスタンス変数でメモ化して高速化を図ったところ、意図しないところでテストが落ちてしまいました。*1

# 変更前
def bar
  # 重い処理
end

# 変更後
def bar
  @bar ||= # 重い処理
end

(結局 foo.reload ではなく foo = foo.class.find(foo.id) のように再取得して上書きました)

実装を追う

同じようなハマりを繰り返さないように ActiveRecord::Base#reload の実装を確認しておきます。
新規に Rails 5.2.1 のアプリケーションを用意し、

# app/models/foo.rb
class Foo < ApplicationRecord
  def reload
    byebug
    super
  end
end

byebug でステップ実行しながら実装を追いました。

def reload(*) # :nodoc:
  clear_aggregation_cache
  super
end

https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/aggregations.rb#L13

def reload(options = nil)
  @marked_for_destruction = false
  @destroyed_by_association = nil
  super
end

https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/autosave_association.rb#L228

def reload(*) # :nodoc:
  clear_association_cache
  super
end

https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/associations.rb#L253

# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
  super.tap do
    @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
    @mutations_before_last_save = nil
    @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
    @mutations_from_database = nil
  end
end

https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/attribute_methods/dirty.rb#L33

def reload(options = nil)
  self.class.connection.clear_query_cache

  fresh_object =
    if options && options[:lock]
      self.class.unscoped { self.class.lock(options[:lock]).find(id) }
    else
      self.class.unscoped { self.class.find(id) }
    end

  @attributes = fresh_object.instance_variable_get("@attributes")
  @new_record = false
  self
end

https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/persistence.rb#L601

(ちなみに ActiveRecord::Persistence は、ここで ActiveRecord::Base に include されています)

というわけで、ActiveRecord::Base#reload では

  • キャッシュを破棄したり各種のインスタンス変数を初期化(っぽい)したりしつつ、 @attributes を置き換えている
  • メモ化用のものなど、自前(?)でセットしたインスタンス変数達は変更されない

ということがわかりました。

共有は以上です。

*1:本来は別インスタンスが行う処理を1つのインスタンスを使い回して行っていた

AWS CodeBuild 入門

morishitaです。 今回は利用しているAWSのサービスの中でも特にお気に入りの CodeBuildについて書きたいと思います。

CodeBuildとは

CodeBuild とはAWSのサービスの1つで、完全マネージド型のビルドサービスです。

ビルドサービスだからといって、プログラムコードのコンパイルしかできないわけではなく、設定次第で様々な処理を実行できます。

CodeBuildの特徴

スペックの高い処理環境を分単位で利用できる

最もパワフルな設定では8vCPU、15GBメモリの環境を使うことができます。EC2のインスタンスタイプで言えば c5.2xlarge に近いスペックです。

例えば20営業日/月に1時間/日の処理が必要として、c5.2xlargeインスタンスをずっと運用するとなると月に$313.30にかかります。
一方、CodeBuildであれば時間単価こそEC2より高いものの、必要な時間だけの課金なので$0.020/分x60分x20日=$24。たった$24しかかかりません。圧倒的なコストパフォーマンスです。

マネージド型なので手間が少ない

EC2インスタンスだって必要な時間だけ起動すればいいじゃない? と思うかもしれません。もちろんそれもできるでしょう。 そのときに準備するのはEC2インスタンスの起動・停止を行うスクリプトとそれを動かすための別のマシンでしょうか。

しかし、CodeBuildならばAPIで実行を指示するだけで、オンデマンドで実行環境がプロビジョニングされ処理を実行し終了します。終了後の後始末も必要ありません。
実行のトリガーはCloudWatch Eventも利用できるので定期実行する場合も他のマシンでAPIを定期実行する必要もなく設定するだけでできてしまいます。 同時に複数の環境を動かす必要が出ても何台でも並行して動かせます。コンソールから実行するだけです。

Lambdaの様に5分制限もなく、必要なコンピューティングパワーを得ながらサーバ管理のない開放感!

様々な実行環境を選択できる

CodeBuildには次のような様々な言語に対応した実行環境が用意されています。

  • Golang
  • Java
  • Node.js
  • PHP
  • Python
  • Ruby
  • .NET Core
  • Docker

これらの環境はDockerイメージとして用意されており、ベースはUbuntu 14.04となっています。

実行するとホスト環境が割り当てられ、指定した環境のコンテナが起動します。 利用者側はホスト環境を意識することなくコンテナの中で実行したい処理を定義します。処理に必要なソフトウェアを追加でインストールすることもできます1。 これ以外の環境が必要ならば、自分で作ったDockerイメージを使うこともできます。
また、Windowsの環境も選択可能です。

Githubと連携可能

CodeBuild で処理するコードはGithubから取ってこれるのはもちろん、Githubへのpushをトリガーに処理を開始することもできます。
例えば、pushするとビルドしてデプロイするなんてことも可能です2。 それ以外にも自動でテストを実行したり、DockerイメージをビルドしたりGithubを起点とした処理系にも組み込みやすいです。

AWSの他のサービスとも連携が容易

前述したCloudWatch Eventとの連携はとても便利です。 CloudWatch Eventによりトリガーすることもできますし、CodeBuildの状態変更をCloudWatch Eventで監視して他のサービスをトリガーすることもできます。

他にCodePipelineCodeDeployと行った他のAWSサービスとも連携しやすいですし、LambdaからCodeBuildをトリガーするのも難しくはありません。

動かしてみよう

ではCodeBuildはどの様に使うのか? RailsアプリケーションのRSpecを実行する例で説明します。

処理の概要は次のとおりです。

  1. テスト対象のソースコードをGithubから取得する。
  2. RSpecを実行するためのdocker-compose を実行する。
  3. テストの結果をファイルに出力して、それらをS3バケットに置く。

CodeBuildプロジェクトの定義

最初にCodeBuildのビルドプロジェクトを定義します。

利用するCodeBuildの実行環境は次のとおりです。

  • 環境イメージ:aws/codebuild/docker:17.09.0
  • コンピューティングタイプ:15 GB メモリ、8vCPU
  • ビルドの対象:Github上の rails-appというリポジトリ

AWSのコンソールを利用します。この例では次の様に定義します。

f:id:HeRo:20180831053352p:plain

buildspec.yml

CodeBuildはbuildspec.ymlというファイルで処理内容を定義します。 このファイルは、Githubから取得するコードに含めておきます。 buildspec.yml自体もソースと共にバージョン管理できて便利ですね。

今回の例の場合、その内容は次のようになります。

version: 0.2

phases:
  pre_build:
    commands:
      - echo Starting pre_build phase at `date`
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
      - mkdir -p ./coverage/
  build:
    commands:
      - echo Build Phase started on `date`
      - echo Running RSpec test sequence
      - chmod -R 755 bin
      - docker-compose -f docker-compose-rspec.yml up --abort-on-container-exit
  post_build:
    commands:
      - echo Post Build Phase started on `date`
      - cp ./log/test.log ./coverage/
artifacts:
  files:
    - '**/*'
  base-directory: 'coverage'

ポイントを説明します。

phases:

phasesで処理を段階ごとに定義していきます。 - pre_build: - build:フェーズのための準備を行います。 - ECRへのログインと、テスト結果を出力するための/coverage/ディレクトリを作成しています。 - build: - メインの処理を実行します。 - RSpecを実行するためのdocker-composeを実行しています。 - post_build: - build:フェースの後処理を行います。 - ログファイルを/coverage/にコピーします。

artifacts:

CodeBuildでは処理の成果物をアーティファクトと呼びますが、このブロックでは そのアーティファクトを指定します。指定されたファイルはS3にプットされます。

この例では、coverageディレクトリ以下のすべてのファイルをS3に出力します。

docker-compose-rspec.yml

具体的にRSpecを実行するdocker-composeの処理内容を示さないとわけがわからないと思いますので、その内容も次に示します。

version: '2'
services:
  db:
    image: mysql:5.6
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    ulimits:
      nofile: 65536
  rails:
    image: "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/rails:base"
    environment:
      RAILS_ENV: test
      RACK_ENV: test
      SECRET_KEY_BASE: pickasecuretoken
    ulimits:
      nofile: 65536
    command: [
      "prehook", "bundle install --with test", "--",
      "prehook", "yarn install", "--",
      "prehook", "bundle exec rake webpacker:compile", "--",
      "prehook", "bundle exec rake db:create", "--",
      "bin/rspec-queue", "spec" ]
    volumes:
      - .:/rails-app
    depends_on:
      - db

ポイントを説明します。

コンテナは2つ

MySQLのdbコンテナとRSpecを実行するrailsコンテナを起動します。 dbコンテナはDockerHubで公開されているMySQLの公式イメージを利用しています。 一方、railsコンテナは毎回ゼロからbundle installyarn installを実行するのは非効率です。 予め実行したベースのDockerイメージをECRのプライベートリポジトリに作っておりそれを利用しています。 テストするソースコードはGithubから取得してカレントディレクトリに配置されます3

railsコンテナのcommand

railsコンテナにはEntryKitをインストールしており、 RSpecの実行前に必要なモジュールのインストールやJavacriptのトランスパイル、DBの生成を行っています。

予めbundle installyarn installを実行したベースイメージを使っていますが、イメージを作成した時点から変更されている可能性があるので再度実行して反映します。 利用しているCodeBuildの8vCPUを有効活用するためにtest-queueを使って並列実行しています。この場合、8並行で実行できます。

見ての通り、railsコンテナはRSPecの実行が終了すれば止まりますが、dbコンテナが止まらないとCodeBuildは処理を終了できません。そのため--abort-on-container-exitオプション付けてdocekr-composeを実行しています。これによりいずれかのコンテナが止まるとdocekr-composeが終了します。

この様に設定して、CodeBuildを実行するとRSpecを実行し、結果がS3バケットに出力されます。 ちなみに、このやり方はいこレポのCI環境でやっていることほぼそのままです。

tech.actindi.net

ハマりどころ

デバッグしづらい

CodeBuildの処理内で標準出力に出力したものはClowdWatch Logsに出力されます。
エラーが起こったときによりどころとなるものはそれしかありません。 地道にechoコマンドを使ってデバッグプリントを出力して調査することになります。

結構辛いです。

したがって込み入った処理はシェルスクリプト等にして、CodeBuild外でよくテストしておいて、 それをCodeBuildで実行したほうが良いでしょう。
私はシェルスクリプトではなくdocker-composeでやっています。

シェルスクリプトは実行権限がない

CodeBuildが取得したソースには実行権限がついていないことがあります。
処理をシェルスクリプトにしてCodeBuild内で実行するには、実行権限を付けてやるか、bashとかrubyといったコマンドに渡してやる必要があります4
それがわからず最初だいぶハマりました。

終了ステータス 0 以外で止まる

各ステップのコマンドは終了ステータス0で終了しないといけません。終了ステータス0以外で終了するとそこで失敗とみなされ終ってしまいます。

終了ステータス0以外で終了しても続行したい場合には次のようにしてとにかく終了ステータス0で終わるようにします。

/bin/bash -c '<終了ステータス 0 以外で終了する何らかの処理> || true'

こんな用途にもおすすめ

Dockerイメージのビルドの他にも次のような用途におすすめです。

  • Dockerイメージのビルド
    • いこレポでは、本番環境にデプロイするDockerイメージのビルドにも活用しています。
    • ベースとなるイメージをDockerHubから取得し、ビルドして ECRのプライベートリポジトリにpushするなんてことができます。
  • バッチ処理
    • いこレポでは開発環境やステージング環境で利用するデータベースを本番データベースから毎日生成しています。
    • VPC上のリソースにアクセスできるようになったのでRDSEC2とも接続でき活用の幅が広がりました。

まとめ

  • 手軽に強力なマシン環境が分単位で手に入る
  • AWSのサービスとの連携もしやすい
  • VPCに接続できるようになりかなり制限が少なくなった

これは使ってみるしかない!

最後に

アクトインディではこのような便利なサービスを活用して、開発したいエンジニアを募集中です。

actindi.net

一緒にランチでもしながらもっと知りたい、話してみたいと思ったら、お気軽に連絡ください!


  1. apt-get コマンドが使えます。繰り返し頻度の高い場合にはインストールを済ませたカスタムイメージを用意し、それを利用したほうが効率的でしょう。

  2. 一つのCodeBuildプロジェクトで行うよりCodePipelineなどと組み合わせてビルド処理とデプロイ処理といったように分離しておいたほうが別の処理を挟んだり組み替えたり、一部だけ実行したりといった小回りがきいていいと思います。

  3. buildspec.ymlやdocker-compose-rspec.ymlもソースコードの一部としてGithubから取得する

  4. Githubにコミットする時点で実行権限を付けておりCodeBuildで直接取得するときには大丈夫だと思います。CodePipelineと組み合わせた場合にはS3を経由するのですが、そこで実行権限が外れてしまうようです。

HTTP/HTTPS判定をnginx側で判定するようにしました

こんにちは、endoです。

いこーよのHTTP/HTTPS判定をアプリケーション側から、nginxで行うようにしました。

rails5へのアップデート作業の途中経過報告に書いていたことを実現しました。

理由としては、下記の通りです。

  • アプリケーション側で行うより、ALB/nginx側で行う方がコンピュータに優しい
  • rails5のupdate作業中にssl_requirementのメンテナンスをなくしたかった

対応としては、下記のことに注意しないといけません。

  • HTTPSリダイレクト
    • 一部HTTPでもアクセス可能
  • ALBのhealth check対応

HTTPSリダイレクト

いこーよはALBでHTTPS判定を行っているので、その前提でお話しします。

ALBは暗号化処理を終わらせた状態で後続のリクエストを流してくれます。

user    →    ALB    →    nginx    →    rails
      暗号化       暗号解除(平文)

ALBは暗号化処理を解除した証にヘッダーにX-Forwarded-Protoをhttpsという値をつけてくれています。

nginxは$http_x_forwarded_protohttpsかどうかを判定すれよくなります。

httpアクセスの場合は、ALBはヘッダーにX-Forwarded-Protoをつけてこないので、それを利用します。

ALBはヘッダーにX-Forwarded-Protoをつけてこないので、それを利用します。

location / {
  ...

  if ($http_x_forwarded_proto != https) {
    return 301 https://$host$request_uri;
  }

  ...
}

これでHTTPSのリダイレクトは可能になりました。

一部HTTPでもアクセス可能

こちらは特定のURLの場合の設定をnginx側に書きます。


location /foo {
  try_files $uri @app;
}

try_filesでそのまま通常の設定に送ります。

ALBのhealth check対応

health checkをHTTPS通信で行うと暗号を解除しないといけなくなるのでHTTP通信で行います。

ここで気をつけないといけないことです

  • health checkはHTTP
  • health check以外はHTTPS
location /bar {
  # ヘルスチェックはHTTP通信を許可する
  if ($http_user_agent ~ "ELB-HealthChecker") {
      proxy_pass http://app_server;
      break;
  }

  # HTTP通信の場合は、HTTPSにリダイレクトする
  if ($http_x_forwarded_proto != https) {
      return 301 https://$host$request_uri;
  }
  try_files $uri @app;
}

User AgentでALBかどうかを判定します。

リリース時の対応

nginxの設定を反映すれば完了です。

いこーよはマルチAZなので、アクセスを片方に寄せてnginxを起動することもできるのですが、若干めんどくさいですね。

nginxはプロセスで設定を「反映する前」と「反映後」を共存できるので,それを利用して反映します。

sudo /opt/nginx/sbin/nginx -t←設定ファイルを確認する

sudo kill -USR2 `cat /var/run/nginx.pid`←設定後のプロセスを立ち上げてくれて、反映前のプロセスと共存します。
sudo kill -WINCH `cat /var/run/nginx.pid.oldbin`←反映前のworkerプロセスを終了する
sudo kill -QUIT `cat /var/run/nginx.pid.oldbin`←反映前のプロセスを終了する

これでリリースは終了です。

唯一の懸念点は、health checkのUser Agentが変更されたら、health checkでサーバーが落ちていきます。

こんなことがあるのかは、わからないですが、現象さえわかっていれば対応は取れます。

以上です。

補足

ステージングでhealth check対応かどうかを調べていたときの方法です。


curl 'http://staging.net/foo' -H 'User-Agent: ELB-HealthChecker'

curlコマンドのUser Agentを入れて確かめていきました。

参考

無停止でnginxを手動upgradeする

rails5へのアップデート作業の途中経過報告

こんにちは、endoです。

アドベントカレンダー2日目の記事です。

現在、rails5へのアップデート作業をしており、その途中経過報告です。

1 rails4系の最新版にする

2 rails5を動かす

→gemをrails5対応するものにあげる

3 rails4で変更できるDeprecationコードを書き直していく

→Deprecationコードを地道に修正する

4 rails5でしか変更できないもので一気にリリースする

ざっくりと、こんな感じで動いております。

現在は、3の作業の途中です。

体感でいうと、3の入り口で全体の10%ぐらいの気がしています。

では、1の状況から振り返ります。

rails4系の最新版にする

rails4.2.10に上げました。

nokogiriが1.8.1に上がり、確認漏れがあり不具合が出ました。

CMSのDOMのParseをnokogiriで行っていますが、Parseに修正が入っており、今までの書き方では動かない場所がありました。

これは気づきにくいよ・・・(´・ω・`)

rails5を動かす

いきなりgemをrails5に切り替えると、悲しいぐらいすんなり動いてくれません。

その理由は、gemがrails5(actionpack/railtieなど)に対応していないからです。

ここは地道に上げていきましょう。

  • acts-as-taggable-on 3.5.0 → acts-as-taggable-on 5.0.0
  • coffee-rails 4.0.1 → coffee-rails 4.2.2
  • delayed_job_active_record 4.1.1 → delayed_job_active_record 4.1.3
  • capistrano 2.15.5 → capistarano2.15.9(sprocketsのdeploy対応)
  • slim-rails 3.0.1 → slim-rails 3.1.3
  • sass-rails 4.0.3 → sass-rails 5.0.6
  • sprockets 2.11.0 → sprockets 3.7.1
  • rspec-rails 3.4.0 → rspec-rails 3.7.0
  • jbuilder 2.2.5 → jbuilder 2.7.0
  • will_paginate 3.0.7 → will_paginate 3.1.6

使用しているgemで最低限上のものを上げないとrails5が動きませんでした。

気持ちにはiphone5からiphone8にアップデートするぐらいのアップデートでした。

gem updateによる副産物

今までバグがたまに出ていた箇所の修正がされており、わざわざバグ対応する手間が省けました。

https://github.com/mbleigh/acts-as-taggable-on/pull/809

上のプルリクによって、たまにいこーよ内で起きていた問題も解消されました。

gemをアップデートすることは大事ですね。

rails4で変更できるDeprecationコードを書き直していく

rails5が動いたからといって、今までの上記のgem以外が動かないとは限りません。

pry-railsは動いてくれなかったので、アップデートしました。

DEPRECATION : alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.

rails consoleを叩くだけで、簡単にDeprecationが出てくれるので、本当に助かります。

ここでDeprecationで出ているものは、gemの場合もあります。

gem側が修正していないなら、forkして直すか、捨てるしかありません。

時代とともにソースコードは変化する

話は変わりますが、いこーよはhttps化が完了しております。

https://github.com/actindi/ssl_requirementhttpsの判定をしております。

ただ、このgemでDeprecationが出ているので、http/httpsの判定をnginx側に全部寄せようとしています。

今まではアプリケーションコードで書く方が柔軟でしたが、現状はhttps化になっているので、アプリケーション側で設定するよりはnginx側に任せるよう方が役割がハッキリします。

一部httpの接続を許可する場所もありますが、複雑になりそうな気配もないので、これをきっかけに変更します。

そして、gemの役割を終えてもらいます。

総括

想像しているよりやること多いなって感じたのが本音です。

ただ、gemのアップデート作業によって不具合が修正されたり、いらないgemを消したりできるので、こういうのは嬉しいですね。

この作業終わったら、常にgemを最新の状態に保てるような体制を構築したいと思います。

以上です。

Railsのログを awslogs で Cloudwatch Logs に出力する

morishita です。

今回はいこレポでのログ出力について紹介します。

いこレポの動作環境

いこレポは ElasticBeanstalk を利用してアプリケーションサーバを稼働させています。 ElasticBeanstalk ではプラットフォームを選択できますが、 Multi Container Docker を利用しています。 この場合、実際にRailsが稼働するのは ECS上に起動された Docker コンテナとなります。

Dockerではコンテナ内に永続化するデータを保存しないことが基本ですが、ログも コンテナ削除時に消えていい情報ではないので何らか外に出す必要があります。

その方法としては次の方法があるかと思います。

案1. ホスト側のディレクトリをマウントして、そこにログを保存する
案2. Fluentd でログサーバに転送する
案3. Cloudwatch Logsに転送する

いこレポではECSインスタンスも基本イミュータブルに運用しており、デプロイ毎に新たなインスタンスを起動して、古いのは捨てているのでホスト外に出す仕組みがもう一段必要になります。よって案1は却下しました。

Fluentd を使うと柔軟なログ転送が可能でElasticSearchに送ったりもやりやすいのですが、S3に格納するにしてもログを一旦受けるログサーバを立てる必要があります。ログサーバを立てて運用する手間を惜しんで案2も今回は見送りました。

いこレポで採用したのは案3のClowdwatch logsに出力するという案3の方法。 それについて以下に述べます。

Dockerの設定

Dockerには ロギングドライバという仕組みがあり、標準出力に吐いたログを設定されたロギングドライバ経由でハンドリングすることができます。

ロギングドライバには fluentd 1syslog がありますが、Clowdwatch Logsに出すには awslogs を使います。 このログドライバは当然、ElasticBeanstalk (具体的にはECS)でも利用できます。

ElasticBeanstalk ではコンテナの定義を Dockerfilecocker-compose.yml ではなく、Dockerrun.aws.json というAWSの独自フォーマットのファイルに記述します。

次のように設定してやれば、awslogsロギングドライバを利用できます。
logConfiguration がロギングドライバの設定部分です。

{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "rails",
      "image": "<DockerイメージのURL>",
      "essential": true,
      "memory": 1024,
      "entryPoint": ["<略>"],
      "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "<ロググループ名>",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "production"
          }
      }
    }
  ]
}

logDriverawslogsを指定し、options でログの格納先となる Clowdwatch Logsのロググループを指定します。

ログを出力するためには設定以外に次の2点が必要となるので注意です。

  • 出力先のロググループ は予め作っておくこと
  • ElasticBeanstalk でインスタンスの稼働に利用されるロールがそのロググループへのアクセス権を持っていること

Railsの設定

さて、Docker側の設定ができれば、ログの出力先を標準出力にするだけで Clowdwatch Logs にログが保存されるようになります。

Rails5.1では production.rb にもともと次のように定義されており、RAILS_LOG_TO_STDOUT という環境変数があれば、標準出力にログが出力されるようになっています。

  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end

ElasticBeanstalk では管理コンソールから環境変数を設定できるのでそれを利用すれば、設定ファイルを変更することなくログを標準出力に切り替えられます。

ログ出力のフォーマット変更

Railsのログは 1リクエストに対して複数行で出力されます。 例えば次のように。

Started GET "/" for 172.18.0.4 at 2017-08-16 02:10:36 +0000
Processing by TopController#index as HTML
  Rendering top/index.html.slim within layouts/application
  Rendered top/index.html.slim within layouts/application (***.*ms)
  Rendered layouts/_header.html.slim (**.*ms)
  Rendered layouts/_footer.html.slim (**.*ms)
Completed 200 OK in ***ms (Views: ***.*ms | ActiveRecord: **.*ms)

Clowdwatch logsでは1行が1レコードとなってしまうので、どのレコードからどのレコードまでが一連のログなのか追う必要があり、非常に見づらくなります。 1行のJSONで出力すると1レコードとして一連のログを記録できる上に、パースして見やすくしてくれるので、JSONで出力するようにログフォーマットを定義するようにしました。

いこレポではログフォーマットに roidrage/lograge: An attempt to tame Rails’ default policy to log everything. を利用しています。

まず、production.rb に次の設定を追加します。

  config.lograge.enabled = true
  config.lograge.formatter = Lograge::Formatters::Json.new
  config.lograge.custom_payload do |controller|
    {
      host: controller.request.host,
      remote_ip: controller.request.remote_ip,
    }
  end
  config.lograge.custom_options = lambda do |event|
    exceptions = %w(controller action format id)
    {
      time: event.time,
      host: event.payload[:host],
      remote_ip: event.payload[:remote_ip],
      params: event.payload[:params].except(*exceptions),
      exception_object: event.payload[:exception_object],
      exception: event.payload[:exception],
      backtrace: event.payload[:exception_object].try(:backtrace),
    }
  end

config.lograge.formatter = Lograge::Formatters::Json.new でJSONフォーマッターを使うこと指定して、 config.lograge.custom_payload では 標準で出力されている項目以外を追加したい場合に 各アプリケーションの要求仕様に沿ってそれを追加するように実装します。

lograge.custom_options ではどの項目をどういうキーでJSONに出力するのかを実装します。ここで定義した、ハッシュがそのまま、JSONとしてログ出力されます。

rescue_from で404や例外をキャッチして、レスポンスをレンダリングしている場合、 これだけでは 例外情報がログに入りません。 そこでapplication_controller.rbappend_info_to_payload をオーバーライドします。こうしておけばバックトレースもログに出力されるようになります。

  def append_info_to_payload(payload)
    super
    if @exception.present?
      payload[:exception_object] ||= @exception
      payload[:exception] ||= [@exception.class, @exception.message]
    end
  end

config.lograge.custom_payload を実装せずに、append_info_to_payload のオーバーライドでやってしまってもいいかもしれない。

以上を実施して出力されるログの表示はこんな感じです。 ClowdwatchLog

とても見やすくなります。

最後に

おでかけ先探しに悩むパパ・ママを助けてくれるエンジニアを募集していますので、よろしくお願いします。

脚注

  1. じゃあ、 fluentd のドライバを使えば、ログサーバなしで fluentd で運用できたんじゃない?と思われるかもしれませんが、ロギングドライバがやってくれるのはプロトコルのペイロードに載せて転送してくれるところ。例えばS3に保存するにはどこかで一旦受けて、fluentdの S3アウトプットプラグインでS3に送ってやる必要があるのでログサーバの運用が必要となります。 

DelayedJobのバックグランド処理でサーバーが落ちた時にすること

こんにちは、endoです。

今回はDelayedJob先生のバックグラウンドの処理が重くて、スワップが発生してサーバーがお亡くなりになりました。

対処として、サーバー再起動を行いました。

この時、サーバー再起動で処理は繰り返されないだろうと勘違いしていました。

弊社ではDelayedJobの設定でリトライをしないようにしております。

詳細はDelayed::Job で絶対にやっておいた方がいいたった1つの設定 をご確認ください。

視界、夜中に再度実行されてまた、サーバーがお亡くなりになりました・・・

  1. サーバー再起動
  2. 8時間後にもう一度再実行←こいつの正体が意味わからない

設定を確認しました。

config/initializers/delayed_job_config.rb

Delayed::Worker.max_run_time = 8.hours ←こいつ

DBに書き込みをしていないので、DelayedJobが生きていると判断してくれて、再度実行するように気を利かせてくれました。

なので、DelayedJobで落ちるようなことがあったら、確実に失敗させましょう。

Delayed::Job.find(x).fail!←IDは適当に書き換えてください。

以上です。

ActiveRecord::enum を拡張する gem を作りました

kawaguchiです。
iko-yo.net で使われている module を別プロジェクトで使いたくなったので gem にしました。
https://github.com/jiikko/active_record-enum_with_label

class User < ActiveRecord::Base
  include ActiveRecord::EnumWithLabel

  enum_with_label :alert_status, {
    alert_status_none: 'なし',
    alert_status_mail_sent: 'メール送信',
    alert_status_telephoned: '電話',
  }
end

User.alert_status_labels # => ['なし', 'メール送信', '電話']
user = User.create(alert_status: :alert_status_none)
user.alert_status_label            # => 'なし'
user.alert_status_before_type_cast # => 0

ビューが綺麗にかけます。

iko-yo.net を rails4.2 にアップグレードしました

kawaguchiです。
先日、iko-yo.net の rails を 4.1 から 4.2 へアップグレードしました。

大きなアップグレードといえば、 変更行数が多くなるので github.com のプルリクではすべての差分が表示できなくなったり、 コードレビューに時間がかかり開発者にストレスがかかりがちです。

この問題を避けるために、今回のアップグレードは master ブランチでも動く変更に限り、 適宜 master ブランチへマージしていくことにしました。

その結果、master ブランチでも動くように動作確認が若干のオーバーヘッドになりましたが、 巨大ブランチのマージをしないことで、コードレビュー負荷の軽減や精神的に余裕ができました。

以上。