ImageFluxの導入について
イートスマートでは複数のサービスを運営していますが、各サービスで画像を扱う際に、オリジナル画像から独自でサムネイル画像を作成しています。 その場合、サイトリニューアル時に、速度低下やディスク使用量、ディスクI/O負荷等の懸念がありました。 それらを解消する為に、CDNでキャッシュし、画像の加工を柔軟に扱う目的で、今回ImageFluxを導入することにしました。
CDNとは
コンテンツ・デリバリー・ネットワーク(Contents Delivery Network)の略で、ウェブコンテンツを配信するのに最適化されたネットワークのことを意味します。 Webサイトにある画像や動画・CSSなどの静的コンテンツを、Webサイトが管理されている「サーバー」とは、別の(CDN)サーバーにキャッシュし、そのサーバーで代わりに配信することで負荷分散され、Webサイトの表示速度向上が見込めます。
ImageFluxとは
ImageFluxとは、オリジナル画像をベースに画像の拡大・縮小/切り抜き/フォーマット変換等を、URLパラメータ指定することで生成してキャッシュし、代わりに配信してれるクラウドサービスです。
導入の目的
サイトリニューアルを行う際、画像サイズの変更等を容易行える
サイト速度向上が見込める
サムネイル画像を独自で持たないことで、ディスク使用量の削減が見込める
(サムネイル画像を作成しない事で)サーバーのディスクI/O負荷を軽減できる 等
導入して分かったこと(2018/9/20時点)
拡張子と画像形式が一致していない場合の扱い
画像判別には拡張子を利用しておらず、対応画像(JPEG、PNG、GIF)であれば問題ないそうです。
※拡張子が無くても問題なし対応外(BMP等)の画像について
対応外の画像が入力された場合、ステータスコード500を返すそうです。
※対応外の画像は、ImageFluxを通さない対処が必要です。WebPの対応ブラウザは Chrome/Operaの2種類で、どちらもiOSへは未対応とのことでした。
※Chrome(Android)、Android ブラウザへは対応
導入手順
導入手順について簡単に記載します。
契約書の記載、送付
SSL証明書発行
SSL証明書ですが、Let's Encryptは(3ヶ月更新の為)運用負荷が掛かりNGでとのことでした。結果、JPRSのドメイン認証型(972円/1年)を採用しました。SSL証明送付
発行した証明書をImageFlux担当者に送付して、ImageFlux側で証明書を設定してもらいます。ImageFluxアカウント発行
ImageFlux側の作業が完了するとアカウントが発行されます。
※ImageFlux+SSLを採用した場合、発行迄に5営業日程掛ります。オリジン設定
ImageFluxの管理コンソールにログインして、オリジンサーバーの追加を行います。
※オリジンサーバーを追加すると、ImageFlux側のドメインが発行されます。
今回は導入迄ですが、導入結果について改めて報告できたらと思います。
仮想環境上でrailsを動かすまでの流れ
今回は、イートスマート1年目エンジニアが仮想環境を構築後、railsを起動するまでの一連の流れをざっとみていきたいと思います。※VirtualBox及びCentOS7の各種設定については、割愛させて頂きます。
環境について
・Windows 10
・VirtualBox 5.2
・CentOS7
・Ruby 2.5.0
・Rails 5.2.0
パッケージのアップデート及びgitのインストールなど
sudo yum update
sudo yum install -y git
sudo yum install -y bzip2 gcc openssl-devel readline-devel zlib-devel
rbenv(rubyのバージョン管理に必要)及びruby-build(rubyのインストールに必要)の設定
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
上記のクローン後、~/.bash_profileなどにPATHの設定をして反映させる。うまくパスが通ると
rvenb -v
でバージョンの確認ができます。
事前の準備を終えた後、Ruby,Railsをそれぞれインストールしていきます。
Rubyのインストール
rvenb install -l
インストール可能なrubyの一覧が確認できる
rvenb install 2.5.0
開発に必要なrubyのバージョンを入れる
rvenb global 2.5.0
システム上で使いたいrubyのバージョンを指定
ruby -v
rubyのバージョン確認ができるようになる
which ruby
適宜パスが問題ないか確認
Railsのインストール
gem update --system
Rubygem本体のアップデート
gem install --no-ri --no-rdoc rails
ドキュメントを不要にするオプションをつけることでgemのインストールが早くなる
gem install -v 5.2.0 rails
バージョン指定してインストール
rbenv rehash
~/.rbenv/shims/以下にコマンドを置いて、変更を反映させる
ざっくりではありますが、以上が大まかな流れになります!
rails sが成功するまで、いくつか難所がありますが基本❶ターミナルのエラー通りに試しパッケージなどで足りないものがあれば補っていき❷Google検索で先人達がハマった症例を参考に進めていけばゴールにたどり着けるはずです。
最後に自分がハマったポイント
bundle install
で、gem一式をインストールすればほぼ設定完了なのですが、よくあるハマりポイントとして、rmagick(ruby上での画像処理に必要なライブラリ)とimagemagickのバージョンの対応が合わないと、rmagickがインストールできないことがあります。
以下の記事が代表的なケースの処方箋になります。
しかし、私のケースではそもそもimagemagickが入ってなかったことで、rmagickのインストールができないということが問題にハマりました。結局、yumからimagemagick及びそれに関連するライブラリを入れ解決。rmagickの利用には、imagemagickが必要になるので、同じような問題に当たった時にはその周辺の設定を疑っていくことで解決が早くなるかと思います。
参考:ImageMagick - Wikipedia
※おじい様の魔法使いのアイコンが素敵です。
Nginx/Apacheのアクセスログを可視化してみる
今回は、Nginx/Apacheのアクセスログの可視化について書きたいと思います。
イートスマート社では、WEBサービスの解析にGoogleアナリティクスを利用しています。 Googleアナリティクスでサイトへ訪問するユーザーやアクセス状況の分析を行い、日々サービスの改善につなげています。 しかし、Googleアナリティクスだけではユーザーからのリクエストへどのような応答を行ったのか詳細はわかりません。 そこで、Nginx/Apacheのアクセスログを可視化することにしてみました。
アクセスログの可視化
アクセスログの可視化には、Kibanaを利用することにしました。 KibanaとElasticsearchの環境構築にはDocker Imageを利用しました。
アクセスログの設定変更
大量のアクセスを捌くためにサーバのチューニングは行っていましたが、応答の詳細を把握するためにアクセスログの項目を見直しました。
Nginxへ設定したのは以下の項目です。
項目 | 説明 |
---|---|
$request_uri | リクエストされたページを判別するため |
$status | ユーザーへの応答を把握するため |
$request_time | ユーザーへの応答にかかった時間を把握するため |
$upstream_addr | リクエストを処理したバックエンドサーバを特定するため |
$upstream_status | バックエンドサーバの応答を把握するため |
$upstream_response_time | バックエンドサーバの応答にかかった時間を把握するため |
$cookie_JSESSIONID | リクエストされたセッションを特定するため |
Apacheへ設定したのは以下の項目です。
項目 | 説明 |
---|---|
%T | ユーザーへの応答にかかった時間を把握するため |
%D | ユーザーへの応答にかかった時間を把握するため |
%{JSESSIONID}C | リクエストされたセッションを特定するため |
%{%Y-%m-%dT%T}t.%{msec_frac}t%{%z}t | Kibanaのリクエスト日時として利用するため |
既存のログ解析スクリプトへの影響を少なくするため、末尾に追加しました。
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %T %D %{JSESSIONID}C %{%Y-%m-%dT%T}t.%{msec_frac}t%{%z}t" combined-es
以上の設定を反映することで、可視化に必要なデータを揃えることが出来ました。
アクセスログの登録
Elasticsearchへアクセスログと登録する方法として、Bulk APIを利用しました。 当初はtd-agentを利用して登録しようとしましたが、以下の理由から利用を諦めました。
- td-agentが導入されていないサーバが存在する
- 本番環境のネットワークからElasticsearchが稼働するネットワークへ直接接続出来ない
登録するアクセスログは前日分のバックアップを利用します。(このため、可視化出来るのは翌日以降になってしまいます。) Bulk APIで登録するため、アクセスログをJSON形式へ変換する必要があります。JSONへの変換は、Nginx/Apacheそれぞれ以下のように行いました。
Nginx
JSON=`echo "${line}" | perl -F'\t' -nale '%h=map{split/:/,$_,2}@F;print"{\"index\":{}}\\\\n{\"\@timestamp\":\"$h{time}\",\"request_uri\":\"$h{request_uri}\", \"status\":$h{status}, \"request_time\":$h{request_time},\"jsessionid\":\"$h{jsessionid}\"}"'`
JSON=`echo ${line} | awk -F'\\\\[|\\\\]|"' '{print $4" "$5" "$9}' | awk '{print "{\"index\":{}}\\\\n{\"@timestamp\":\""$6"\", \"request_uri\":\""$2"\", \"status\":"$4", \"request_time\":"$7/1000000"}"}'`
まとめ
以上で、Nginx/Apacheのアクセスログの可視化を行うことが出来ました。 ここから、可視化したい内容のVisualizationやSaved Searchを作成してDashboardを作ることになります。 いまのところリクエスト応答の可視化に必要な項目に絞っていますが、今後必要に応じて項目の追加が行われると思います。
docker+Node.js(express+pg-promise)で簡単なAPIサーバーを作成
インフラの構成を変更して自由度が上がったので、サービスに必要な簡単なAPIをdocker+Node.jsで構築してみました。 eatsmart.hatenablog.com
今までNode.jsは、sassを使うために呪文のようにnpmを使った程度なので、一から調べながら始めたのですが、結構簡単にできたなあという印象です。
まあ、簡単な処理しかしないAPIですが。
環境の構築
まずは、環境を構築します。
Node.jsのプロジェクトを作るために、プロジェクトのルートフォルダで
$ npm init
と実行します。プロジェクトの諸元を聞かれるので、適当に答えます。 これにより、プロジェクトフォルダにpackage.jsonファイルが生成されます。
次に、Node.jsでRESTサービスを作るには、 Express - Node.js Web アプリケーション・フレームワーク というライブラリを使用するのが簡単らしいので、express(とbody-parser)をプロジェクトにインストールします。
$ npm install express --save $ npm install body-parser --save
ここで、--saveオプションを付けることで、プロジェクトのpackage.jsonに依存関係を追記してくれるようです。
package.json
"dependencies": { "body-parser": "^1.18.3", "express": "^4.16.3", }
さらに、今回はDBとしてPostgreSQLに接続するために、pg_promiseというライブラリを使用します。
$ npm install pg-promise --save
これで、package.jsonは、以下のようになりました。
"dependencies": { "body-parser": "^1.18.3", "express": "^4.16.3", "pg-promise": "^8.4.6" }
実装
次は実装です。この辺はNode.js+expressの作法を調査して則ることにしました。
expressではリクエストのエンドポイントをディレクトリ単位でルーティングするのが良さそうですが、今回は現時点では機能が少ないので、アプリケーション本体のapp.jsと、データ(今回はタイムライン)の一覧を返すアクションのtimeine.jsで構成することにしました。
プロジェクトのルートから
/ ├─app/ │ ├─app.js │ └─routes/ │ └─timeline.js └─package.json
という構成にしました。 (express-generatorというのを使って構成するのも良さそう。)
あとは、ざっくりapp.jsは
const express = require('express'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: true })); /* 開発環境でのCORS対応のため app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); */ app.use('/timeline',require('./routes/timeline')); //サーバ起動 const port = process.env.PORT || 3000; app.listen(port); console.log('server start listen on port ' + port);
としました。
/timeline以下をtimeline.jsにルーティングする設定をして、サーバーを起動する感じです。
途中のコメントアウトは、ローカル環境でwebサーバーとAPIサーバーを立てるとドメイン(localhostのポート番号)を違えるため、CORSに引っかかってしまうので許可するための設定です。
timeline.jsは、
const express = require('express'); const router = express.Router(); const pgp = require('pg-promise')(); const db = pgp(process.env.DB_CONNECT_STRING); router.get('/list', function (req, res) { let name = req.query.name; db.any("SELECT TIMELINE_TEXT FROM TIMELINE_TABLE WHERE TIMELINE_NAME=$1",[name]) .then(function (data) { let list = []; for(let result of data){ list.push(result['timeline_text']); } res.json(list); }); }); module.exports = router;
としています。/timeline配下の/listを処理するようにしています。
DB接続は、環境変数で与えるようにしました。 クエリパラメータで与えられたnameという値をSQLのプレースホルダーに渡して、結果の一覧をJSONで返却しています。
動作確認
DB接続先文字列を環境変数で与えるようにしたので、APIサーバーの起動は、プロジェクトルートフォルダで
$ export DB_CONNECT_STRING=postgres://[user]:[password]@[server]:[port]/[DB名] $ node app/app.js
とします。
curlコマンドで動作の確認ができました。
$ curl http://server:3000/timeline/list?name=test ["timeline text 1","timeline text 2","timeline text 3","timeline text 4","timeline text 5"]
dockerコンテナ化
次に、本番サーバー用に、dockerコンテナ化します。 公式のnodeのdockerイメージがあるので、プロジェクトフォルダを詰め込んでnpm installするだけで良さそうです。
Dockerfileは以下のようにしました。
FROM node:10 MAINTAINER H.Miyake ADD app ./app ADD package.json ./ RUN npm install CMD ["node","app/app.js"]
このファイルをプロジェクトのルートフォルダに置いて、gitにコミットします。 その後、(最近git-flow的にやっているので)releaseブランチを切って、
$ docker build -t [tag_name] http://XXXXX/XXXX/XXXX.git#release
でビルドできます。(以下でやっていましたね。)
あとは、imageをpushしておいて、本番サーバーで
$ docker pull [tag_name] $ docker run -d \ --name [name] \ --restart=always \ --network=[docker network名] \ -e DB_CONNECT_STRING=postgres://[user]:[password]@[server]:[port]/[DB名] \ -it [tag_name]
で起動できます。
docker runのオプションとか、色々とネタはありますが、とりあえずそんな感じで!
Gitの基本操作について
EatSmartの新人エンジニアが第二回目のブログを更新したいと思います。
今回はGitの基本操作についてまとめてみました。
Gitとは
分散型バージョン管理システム
ローカルにリモートリポジトリの複製を作成し、複数人は各々のローカルで変更履歴を利用して自由にファイルファイルの編集やコミットができる
もともとはLinuxもソースコードを効果的に管理すために開発された
主なツール:Github,GitBucket,BitBucuket,GitLab
◇リポジトリ
ファイルやディレクトリの状態を記録する場所。保存された状態は内容の変更履歴として格納されている。変更履歴を管理したいディレクトリをリポジトリの管理下に置くことでそのディレクトリ内のファイルやディレクトリの変更履歴を記録することができる
◇コミット
ディレクトリやファイルの状態を記録するための操作のこと
作業ディレクトリ上で一区切りついた時にコミットしてそれまでの作業を一旦保存する
記録するファイルを一時的に登録する場所をインデックスという
◇ブランチ
作業履歴を枝分かれさせて記録していくためのもの
◆ masterブランチ
リポジトリに最初のコミットを行うと自動で作成されるブランチ
ブランチは複数作ることができブランチを作るにはすでにあるブランチをコピーしなくてはいけない
ブランチには名前を作ることができ開発者は自由にブランチを移動できる
最終的に全てのブランチはmasterブランチに結合される
各開発者は作業ごとにブランチを作る (=ブランチをきるという)
◆ブランチの作成
git branch ブランチ名
◆ブランチ間を移動する
git checkout ブランチ名
ローカルでの作業からリモートにアップデートするまでの流れ
ワーキングツリー ⇒ インデックス(ステージング環境)◆ワーキングツリー
⇒ ローカルリポジトリ ⇒ リモートリポジトリ
ファイルの編集作業場所でファイルの編集や追加、削除などを行う
◆インデックス
コミットするためのファイルを記録する場所
ワーキングツリーで編集したファイルは git add でインデックスへ移動させる
◆ローカルリポジトリ
リモートリポジトリにアップロードするためコミット履歴とファイルを記録する場所
インデックス上のファイルをgit commit でローカルリポジトリにコミットする
◆リモートリポジトリ
複数人で共有する場所
ローカルコミットしたファイルをgit pushでリモートリポジトリにアップロードする
◆プルリクエスト
作成したブランチをmasterブランチにマージする時の確認作業のこと
差分を確認することができ、他の開発者から確認やレビューをしてもらうことができる
◆ブランチをマージする
マージしたブランチは削除する
コマンドの基本操作
git add ワーキングツリー上で編集したファイルをステージング環境へ追加する
git commit ローカルコミットを実行する
git commit -m 新規コミットを作成する
git commit -amend 直前のコミットを上書きする
git push ローカルリポジトリのブランチをリモートリポジトリへアップロードする
git log コミット履歴を表示する
git log --merge マージコミットだけを表示する
git reflog HEADの履歴一覧を表示する
git reset HEADの位置を変更する
git remote リモートリポジトリの最新状態をローカルリポジトリのリモート追跡ブランチにダウンロードする
git merge 指定したブランチを現在のブランチに統合する
git pull 最新のリモートリポジトリの内容をローカルリポジトリに反映する
git stash まだローカルコミットしていないファイルを一時的に待避させる
git cherry-pick 他ブランチの特定のコミットを現在のブランチに取り込む
Gitを使い始めた時はマスターブランチに結合?プルする?ワーキングツリーって何?
仕組みを理解するのに凄く苦労しましたがGitHub Desktopを使ったりして操作に慣れていきました。GUIで比較的簡単に操作ができるので初学者にはオススメのツールだと思います。
FacebookAPIのpublish_actions廃止について
2018年4月24日にセキュリティ向上の目的でFacebookから発表があった、 "New Facebook Platform Product Changes and Policy Updates" の「publish_actions」廃止について簡単にまとめてみました。
「publish_actions」とは
ユーザーに代わって、アプリがFacebookAPI経由でタイムラインに投稿する際に「publish_actions」という権限が必要でした。
また、アプリで「publish_actions」の権限を取得するには、Facebookの審査を通す必要がありました。
※FacebookAPI Ver1.0の時代は「publish_actions」の取得に関して審査不要でしたが、Ver2.0から審査が必要となりました。
「publish_actions」廃止による影響
4月24日時点で「publish_actions」は廃止(審査にも提出できない)となっており、既にpublish_actions取得しているアプリに関しては、8/1まで使用可能でした。
また、引き続きアプリ経由でユーザーのタイムラインに投稿する場合、Facebookが提供しているシェアダイアログに実装を替える必要があります。
環境構築の際に行った工夫
以前、以下の記事でデータセンターからクラウドへ移行したことを書きました。
このとき、以前から利用していたDockerの適用範囲を広げ、Apache/Tomcat等もコンテナ化しました。 あわせて環境構築のためAnsibleの導入も行い、Nginxなどのミドルウェアの設定を環境ごとに定義して利用することにしました。 これにより、
- 環境構築が簡単になった
- 本番環境・検証環境・ステージ環境の構築手順を共通に出来た
- 本番環境と同一の手順で設定の反映の検証が行えるようになった
など、メリットがありました。
今回は、環境構築の際に行った工夫を書きたいと思います。
Ansibleでの環境構築
まずはインフラ設計を元に、必要なサーバ/ミドルウェアを洗い出します。 次に、セットアップするために必要な手順をAnsibleのplaybookへ書きます。 この時、環境ごとに共通する箇所と異なる箇所を確認して、各環境ごとに異なる箇所の設定をホストへ定義しました。 こうすることで、各環境ごとのセットアップの手順が同一となり、手間と失敗を防ぐことが出来ました。
Ansibleを利用したことで、設定をコードにすることが出来たこと、バージョン管理が出来たので後から変更履歴を把握することが出来たので作業がスムーズに進みました。 移行後に行った変更も同じ手順で作業することで、サーバの追加やミドルウェアのインストールも同様に行うことが出来ます。
環境ごとに異なる箇所は、主に以下の3つです。
サーバの名前とIPアドレス
各環境でサーバの接続手順を統一するため、IPアドレスではなく名前を利用するようにしました。 データベースに接続するにはdb01と指定すれば、各環境のdb01へ接続出来ます。 これにより、各種設定ファイルの定義を環境に応じて切り替える必要が無くなりました。
名前解決のため、内部向けにBINDでDNSサーバをセットアップして利用しています。セットアップ時に環境ごとのIPアドレスを設定ファイルへ反映します。 このセットアップにもAnsibleを利用し、DNSの設定も同様にホストで管理しています。 各環境のローカルIPを共通にするという方法もありますが、障害時やメンテナンス時にサーバを切り替えることも想定し、名前を利用を選択しました。。
サービスに利用するホスト名
もぐナビなら、mognavi.jp/mognavi.dev/mognaiv.stgのように、環境ごとに異なるホスト名があります。 環境ごとに異なる設定は環境変数を参照する方針をとりました。これはサーバ/Dockerともに共通します。 例えばホスト名は、環境変数"HOST_MOGNAVI"を参照して利用するようにしています。 ミドルウェアやDockerコンテナでは環境変数から設定を行うようにすることで、各環境で同一の設定ファイルを利用することが出来ました。
ミドルウェアのパラメータ
各環境で異なるハードウェアのスペックや外部要因を吸収しています。 本番環境ほどリソースに余裕が無い場合や処理能力が不要な場合、ミドルウェアが利用するメモリの量を減らしています。 また、本番環境以外で不要な設定を無効にしたりもしています。
リバースプロキシのセットアップ
リバースプロキシとしてNginxを導入し、コンテンツのキャッシュとSSLの処理に利用しています。 各環境で利用するにあたり、バックエンドの指定をIPアドレスではなく名前で行うようにしました。 念の為パフォーマンスのしましたが、IPアドレスに比べ低下することはありませんでした。
SSLの処理で利用する証明書は、ホスト名ごとのリポジトリで管理しています。 ホスト名は環境変数から環境ごとのものを参照して利用します。
Apacheのセットアップ
URLのリライトやUAに応じたリダイレクト等、アプリケーション固有の処理に利用しています。 ApacheはDockerコンテナで稼働するため、起動時にDockerホストから環境変数を通してホスト名を指定します。 Apacheのconfiでは、以下のように環境変数を参照して利用することが可能です。
# ${HOST_MOGNAVI}の部分がmognavi.jpになる
ServerName ${HOST_MOGNAVI}
# ${HOST_MOGNAVI_SP}の部分がs.mognavi.jpになる
RewriteCond %{ENV:device_type} sp [NC]
RewriteRule ^/$ https://${HOST_MOGNAVI_SP}/ [R=301,NE,L]
設定の中の環境に依存する箇所を環境変数から参照するようにすることで、各環境で共通の設定ファイルを利用出来るようになりました。
まとめ
移行に伴い環境構築で行った工夫を書きました。 Ansibleを利用することで設定のコード化/バージョン管理が出来たので、全体の把握が行いやすくなりました。 以前は環境ごとに設定ファイルを作成しており環境ごとに差異が出来てしまうことがありました。環境変数を利用することで、設定ファイルの共通化とバージョン管理を行うことが出来ました。