GCPのネットワーク設計・序
東京リージョンが来たのでGCPを全力で触ってる途中ですが、ネットワーク、ファイアウォール(FW)の仕様がちょっと変わっていたのでメモ。 (追記事項があり次第更新予定) まだまだ勉強中なので誤りがあればご指摘ください!
ネットワーク設計する際の注意点
「ネットワーク」同士のローカルIPによる繋ぎこみは出来ない。
VPN使えばいけるだろうけど、全体の管理者とかでなければ考えなくていいかと。FWルールに「Deny」は無い。
許可のルールだけ書いてあとは全てDenyという非レガシーな考え方。らしい。- FWルールのdestは何かしらのインスタンス。指定方法は「全て」か「タグ」。
グローバルIP持ってるインスタンスはタグ付け必須。サブネットとか関係無い仕様。 - FWルールにインターネットへのアウトバンド制御不能。
上述のとおり、destはインスタンスしか指定できません。 - AWSでいうプライベートサブネットは無い。
後でオレオレセキュアネットワーク設計を書く予定。 - CloudSQLはグローバルIPでしかアクセスできないので、GCEは静的IPアドレスが必要。 アクセス制御の方法がCloudSQLで設定できるネットワーク(IPアドレス)依存なので、要注意。なんかCloudSQLProxyてのもあるみたいだけど、あんまりメリットというか違いがわからなかった。とりあえずMySQL Proxyのようなモノかと思ったけど、そうでもないっぽいのでちょっと気持ちがふわふわする感じ。。。
- ルートで設定できるネクストホップはネットワークで定義しているサブネットかつ、存在するIP転送設定しているインスタンスのIPアドレスしか指定できない。
存在しないIPアドレスとか自動生成されるデフォゲIP、リンクローカルアドレスのような特殊IPアドレスは指定できませんでした。
今の所、AWSに慣れてるとクセに感じる事があったりするけど、オンプレ経験のほうが長い身としてはGCPのほうが直感的な感じではある。専用用語が少ないってのもあるけど、ベンダが誰の立場となってサービス作ってるか、という視点の違いが大きいんだろうな。
CentOS 7 でswap領域を追加する
AWSで小さいインスタンスを使っていると、updateなり一時的作業なりでメモリ不足のエラーが出ることがままあるのでそういう時用に。
参考
#!/bin/sh SWAPFILENAME=/swap.img MEMSIZE=`cat /proc/meminfo | grep MemTotal | awk '{print $2}'` if [ $MEMSIZE -lt 2097152 ]; then COUNT=$[${MEMSIZE} / 512] elif [ $MEMSIZE -lt 8388608 ]; then COUNT=$[${MEMSIZE} / 1024] elif [ $MEMSIZE -lt 67108864 ]; then COUNT=$[${MEMSIZE} / 2048] else COUNT=4096 fi dd if=/dev/zero of=${SWAPFILENAME} bs=1M count=${COUNT} && chmod 600 ${SWAPFILENAME} && mkswap ${SWAPFILENAME} && swapon ${SWAPFILENAME}
CentOS7の場合、ddコマンドでファイルを作る必要がるので、その辺を弄ったスクリプト担ってます。
Rails4のリレーションを使い分けたい
個人的にはSQL文を組み立てるのは苦手ではありつつも、SQL文をある程度書けるようになるべきだと思っています。とはいいつつも、様々なフレームワークで実装されているモデルの機能というのは素晴らしいと思っています。特にRails4のhas_one,has_manyといったリレーション機能は非常に便利だと思います。
今回、Rails4でリレーションの設定をしたモデルを使っていて、発行されているクエリを見ていると非常に効率の悪いクエリが発生しているのに気付きました。
ざっくりいうと、SNSのようにユーザ一覧ページを表示した時に、一覧表示に必要のない参照先のテーブル情報まで引っ張っていました。一覧で100人表示したら不要なSELECTのクエリが100回(リレーション数によっては200,300となります)発生する動作をしていたので、流石に無いなと思って調べてました。
DB設計がなってない、と言われればそこまでなんですがそこは置いておいて。。。Rails4ではリレーションの設定をした場合、find_by_sqlとかなんとか使っても参照先のテーブルから情報を引っ張るようです。
あまりスマートではないですが、modelファイルをもう一個作って今回は回避しました。
class Users < ActiveRecord::Base has_one :profiles has_many :followers # 略 end
class UsersNonRelational < ActiveRecord::Base self.table_name = 'users' end
親テーブルの一覧が欲しいだけなんだけど、、、って時の対応でした。もっとスマートな方法・機能があれば教えてください。。。
Phalconを使う際に最初にすることリスト
どのようなサービスであっても個人的にほぼ必須と思っている設定をつらつら書き留める記事です。
こんなのもあるといいYOってのがあれば教えてください。
環境
CentOS 7
Phalcon 3.0.1
PHP 7.0.10
名前空間(Namespace)
使わない人はとことん使わないイメージですが、個人的にはあまりデメリットを感じないので必ず利用するようにしてます。
// app/config/loader.php <?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces([ 'Appname\Controllers' => $config->application->controllersDir, 'Appname\Models' => $config->application->modelsDir ]); $loader->register();
ローダに名前空間とディレクトリの関係を定義します。「'Appname' => 'app/'」みたいにするとコントローラとかモデルも同的に定義・読み込みするらしいのですが、少々面倒でも細かく定義するようにしています。サブモジュールとかで階層重なるのを防げたりするので。
// app/config/services.php // 以下を追記(ルーティング設定により不要 $di->set('dispatcher', function () { $dispatcher = new Dispatcher(); $dispatcher->setDefaultNamespace('Appname\Controllers'); return $dispatcher; });
デフォルトのnamespaceをここで指定すると、次のルーティング設定時にnamespaceの指定を省略できます。逆に異なるnamespaceを多用するのであればあまりメリットはでないかもしれません。
ルーティング
ルーティングの実装方法はいくつか用意されていますが、極力読みやすく厳しく設定できると思っている方法を使ってます。といっても公式で紹介されている方法ですが。あと正規表現は適当です。
// app/config/router.php <?php use Phalcon\Mvc\Router; use Phalcon\Mvc\Router\Group as RouterGroup; // デフォルトの動作を無効化 $router = new Router(false); // 末尾のスラッシュを自動的に取り除く $router->removeExtraSlashes(true); // デフォルトルート $router->add('/', ["controller" => "index", "action" => "index"]); // 404のパス設定 $router->notFound(["controller" => "index", "action" => "route404"]); // ここから $blog = new RouterGroup(['controller' => 'blog']); $blog->setPrefix('/blog'); $blog->addGet('', ['action' => 'index']); $blog->addGet('/edit/{id}', ['action' => 'edit']); $blog->addPost('/edit', ['action' => 'create']); $router->mount($blog); // ここまでを1セット return $router;
// app/config/services.php // 以下を追記 $di->setShared('router', function () { $config = $this->getConfig(); require $config->application->routerPath; // configにパスを書いています return $router; });
Groupを使った時のルート指定は「''」とすることで定義可能。
なお、わざと1行で書くようにして見通しをよくしてます。コーディング規約的におかしいとか言われた事もありますが、ルーティング定義はPHPで書かれているとはいえ、個人的には見易さを重視します。
あと、書き方が冗長だ、こうすれば動的に定義できてスッキリする、というのはわかっていますが、明示的に設定した以外の動作を取って欲しくない(特にチームで開発している時)ので、パスやメソッドとか細かく書いてます。
Viewの無効化(View不要時)
APIとか作る際にはViewを無効にするだけで、標準のMVCを使うようにしています。これは学習コストの問題だけなので効率的か、といわれると効率的ではないでしょう。
// app/controllers/ControllerBase.php public function initialize() { $this->view->disable(); }
Viewに処理が到達するまでに実行されればいいので、どこに記述しても問題はないです。APIを作っている時は初期化時に実行させれば記述漏れとかなくなるので、親コントローラのinitializeに記述していることが多いです。
ログ出力
とりあえず実装して、足りない部分はそれぞれのコントローラやモデルで実装します。
// app/config/services.php // 以下を追記 use Phalcon\Logger; use Phalcon\Logger\Adapter\File as FileAdapter; $di->setShared('logger', function () { $config = $this->getConfig(); $logger = new FileAdapter($config->application->applogPath, ['mode' => 'w']); return $logger; });
手っ取り早いのでファイル出力のロガーを実装。syslogやらもあるので必要に応じて変更する。
Exception
エラーはExceptionで処理する事が多かったので、どこのExceptionでも一箇所でコントロール出来るようにします。個人的には設計しやすいのでよく使います。(主要部分のみコード記載)
// app/config/services.php // 以下のように編集 use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Dispatcher; use Phalcon\Events\Manager as EventsManager; use Phalcon\Mvc\Dispatcher\Exception as DispatchException; use Appname\Controllers\ExceptionModule; $di->setShared('dispatcher', function () { $dispatcher = new MvcDispatcher(); $dispatcher->setDefaultNamespace('Appname\Controllers'); $eventsManager = new EventsManager(); // Attach a listener $eventsManager->attach("dispatch:beforeException", new ExceptionModule()); $dispatcher->setEventsManager($eventsManager); return $dispatcher; });
// app/controllers/ExceptionModule.php <?php namespace Appname\Controllers; use Phalcon\Events\Event; use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Dispatcher\Exception as DispatchException; class ExceptionModule { public function beforeException(Event $event, Dispatcher $dispatcher, $exception) { // Handle 404 exceptions if ($exception instanceof DispatchException) { $dispatcher->forward(array( 'controller' => 'index', 'action' => 'show404' )); return false; } // Handle other exceptions $dispatcher->forward(array( 'controller' => 'index', 'action' => 'show503' )); return false; } }
DIのところでbeforeExceptionを独自のものを指定する。いちおう公式に載っている方法ですが、Exception処理部分を別ファイルにして管理しやすいようにしています。(以前、docでもnewして外部ファイルで管理する方法をもうちょっと細かくコード付きで書いてたような記憶がある)
基本的にはinstanceofのif文を必要なだけ追記してException毎に処理を分けていきます。開発時は最初にログ出力処理を書いてデバッグしやすくしたりしてます。
PhalconのRoutingとViewの関係
独自にルーティングを定義する際に、公式docを参考(なので大部分を割愛)に以下のように定義しました。
<?php use Phalcon\Mvc\Router; $router = new Router(false); $router->add( "/", [ "namespace" => 'Appname\Controllers', "controller" => "index", "action" => "route" ]); return $router;
ここで「IndexController.php」の「routeAction」が参照する末端のViewは「app/views/index/route.volt」です。
しかしながら、上記ルーティングの「"controller" => "index",」で指定している「index」を「Index」(頭文字を大文字)とすると、参照しにいくViewは「app/views/Index/route.volt」となりました。
実行されるコントローラは大文字小文字関係なく「IndexController.php」が実行されるが、参照しにいくViewはルーティング時の文字列に影響する模様。「"action" => "route"」は大文字小文字区別されるのでちょっと注意が必要。
これはPHPのクラス名は大文字小文字の区別しなくても動作する仕様からくるものだと思われる。
Phalcon3 + PHP7 導入手順
気づいたらPhalcon3がリリースされていたので、PHP7と一緒に試してみました。
・環境
CentOS7(AWS)
PHP 7.0.10
Phalcon 3.0.1
nginx 1.8
$ sudo yum install epel-release centos-release-scl-rh $ sudo yum install https://centos7.iuscommunity.org/ius-release.rpm $ sudo yum install rh-nginx18 php70u-devel php70u-mysqlnd php70u-fpm gcc libtool pcre-devel php70u-opcache php70u-json $ curl -s https://packagecloud.io/install/repositories/phalcon/stable/script.rpm.sh | sudo bash $ sudo yum install php70u-phalcon $ php -m [PHP Modules] phalcon
公式doc通りではコンパイルエラーが発生し解決できず、試行錯誤しているとphalconブログなるものがあって、そこのレポジトリを利用。なお、公式docでインストールしているパッケージだけでは読み込まれないのでphp70u-jsonを追加でインストールする必要があります。
phalcon - Repositories | packagecloud
インストールは以上で完了なのであとはnginxとphp-fpmの設定を各々の環境に合わせてセットしてください。
Unicornをsystemdで管理する
Rails初心者が典型的なハマり方をしたと思うので戒めの意を込めて。
前提
・CentOS 7系
・SCL版ruby2.3
・SCL版nginx1.8
nginxはscl版でも標準でsystemctlコマンドで制御できるので特に問題なし。
$ sudo systemctl enable(start) rh-nginx18-nginx
unicronは自分でserviceファイルを記述する必要があるが、sclのrubyを使っているので普通にかくと環境変数がことなることでエラーになります。
ということでserviceファイルは以下みたいな感じで記述
[Unit] Description=Unicorn Server [Service] WorkingDirectory=/var/www/html/ SyslogIdentifier=unicorn PIDFile=/var/www/html/tmp/pids/unicorn.pid User=nginx Restart=always ExecStart=/usr/bin/scl enable rh-nodejs4 rh-ruby23 -- unicorn_rails -c config/unicorn.rb -E development -D ExecStop=/usr/bin/kill -QUIT $MAINPID ExecReload=/bin/kill -USR2 $MAINPID
しかし、ちゃんと起動しない。コマンド直打ちでは問題ないことはわかっているので、ユーザ周りを睨んで調査していると、gemで入れたアレコレがみつからないことがわかった。(経緯のメモを忘れた)
原因 「bundle install」でパスを未指定
これは完全にbundleの理解不足でした。パスを未指定で実行していたことで、カレントユーザのホームディレクトリにインストールされるため、systemdで実行した際に「色々ネーヨ」ってなってました。なので、パスを指定してインストールした以後は「bundle exec」をつけて各種コマンドを実行することでbundle installしたgem群の読み込みをよしなにしてくれた。
ということでserviceファイルのExecStartのコマンドを次に修正することで無事解決
ExecStart=/usr/bin/scl enable rh-nodejs4 rh-ruby23 -- bundle exec unicorn_rails -c config/unicorn.rb -E development -D