So-net無料ブログ作成
検索選択

帰ってきた・たぶん難しくないApache2・挑戦!RewriteRule・その4~楽しいProxy~ [Linux(Apache)]

 本来なら、Proxyしたい場合はそれ用のディレクティブ「Proxy」等があるので、そちらを使うのがスジです。
 しかし、RewriteRuleでもProxyすることが出来たりしますし、むしろこっちの方が便利だということもあったりする訳なのです。そこで、RewriteRuleでProxyする方法を解説しておきます。



その1:反則技編 ~とにかくProxyする~

 まずはいきなり反則技から行きたいと思います。
 とにかくProxyします。このような場合、本当なら「Proxy」ディレクティブを使うのがスジです。RewriteRuleを使うべきではありません。しかし、解説としてはこれが一番判りやすいのでコレから先に解説したいと思います。
 なお、セキュリティ上の問題を多分に孕んでいますので、これで事故が起こったとしても当方は一切関知しませんので念のため。

 RewriteRule ^/(.*) http://otherserver/$1 [P]

 こんだけ。
 Proxyディレクティブを使う場合はもうちょっと指定が必要になるんですが、RewriteRuleディレクティブでやる場合はたったこんだけで終了です。これで、このサーバにきた全てのリクエストが、別のサーバ「otherserver」に中継されます。リダイレクトじゃありません。中継です。大事なことなので(ry

 ポイントになるのはRewriteRuleディレクティブの第3パラメータである[P]です。こいつが「Proxy」を意味しています。ここが[R]の場合はリダイレクトされます。つまり、ブラウザがotherserverにアクセスしなおすことになるんですが、今回はここが[P]なので、このサーバがotherserverにアクセスを中継してデータを取得するということをします。



その2:アレはProxyする。コレはProxyしない。

 RewriteRuleディレクティブを使ってProxyする最大の意義は、RewriteCondディレクティブやRewriteMapディレクティブといった機能を組み合わせてProxyする・しないを制御出来ること、あるいはProxy先を自在に書き換えることが出来ることです。

 たとえば、RewriteCondと組み合わせて、「自サーバにファイルがあればそれをクライアントに返し、なければProxyとしてotherserverに取りに行く」みたいなシナリオを想定してみます。するとこんな感じに。

 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteRule ^/?(.*) http://otherserver/$1 [P}

 RewriteCondが今まで説明しなかったような記述なんで説明します。
 サーバ上にその名前のファイルがあるかどうかを判定する場合、-fという記述ができます。これに「!」を付けた場合には否定の意味を持ちますので、「REQUEST_FILENAME」に指定されているファイルが無い場合にRewroteCondが成立することになります。
 ファイルが存在する場合は続くRewriteRuleは実行されませんので、他に無ければ自サーバ上にあるファイルをそのままクライアントに返します。ファイルが無ければRewriteRuleが実行されますから、otherserverにファイルを取得しにいくことになります。

 また、User-Agentに応じてproxy先を変更するなんてこともRewriteRuleならお手の物です。

 RewriteCond %{HTTP_USER_AGENT} iPhone
 RewriteRule ^/?(.*) http://otherserver/iphone/$1 [P]
 RewriteRule ^/?(.*) http://otherserver/$1 [P]

 とかやっておけば、(判定がちょっと雑ですがw)iPhoneとそれ以外のUAとでProxy先を変更することも出来ます。


その3:そもそもproxy先をひん曲げたい

 CGIが動作するサーバを直接インターネットに曝すなんてとんでもない!というしごく真っ当なサーバ管理者なら、CGIサーバの前にリバースProxyサーバを立ててそこからCGIにアクセスするようにしたいなんていう需要もあるでしょうね。当然、静的なhtmlページや画像ファイルはそのままWebサーバから配信し、プログラムが必要な所ではCGIサーバに代理アクセスをしてクライアントのその結果だけ返すなんてことをしたくなるわけです。
 ひとまず、単純に、「ファイルがあればそれを返し、ファイルが無ければURL部分をパラメータ化してCGIに処理を渡す」なんてことを表現するならこんな感じでしょうか。

RewriteCond    %{REQUEST_FILENAME}    !-f
RewriteRule    ^/?(.*)    http://cgi-server/program.cgi?param=$1    [P]


 このような場合、例えばhttp://myserver/index.html というファイルが存在すればそのファイルをクライアントに返します。しかし、http://myserver/shop/item/54 みたいなリクエストが来た時にそのようなファイルが存在しないときは、CGIサーバにリクエストを中継します。その時のURIは http://cgi-server/program.cgi?param=shop/item/54 という具合になります。CGIが出力する結果は自サーバを経由してクライアントに送信される…ということになります。

帰ってきた・たぶん難しくないApache2・挑戦!RewriteRule・その3~RewriteMapを使う~ [Linux(Apache)]

 そうか…。もう6年とか経過していたのか…。
 何もかもが懐かしい…。(遠い目


 URLの読み替えをしたい場合にRewriteRuleを使う…という話をしてましたが、ファイルが決め打ちできるとか、正規表現で一定のルールに従って読み替えができる…というようなケースでは割と簡単にお役に立ってくれました。
 ところが…。
 たまに有りませんか?

 サイトのデザインを一新したんだけど、ページにリンクされている画像ファイルとかバナーとかも全部差し替えたい。でも、他のサイトで直リンクしているバナーもあるようなんだけどもう把握出来ていないので、古いバナーについては新しいバナーにサーバ側で読み替えて欲しいんですけど。

 …みたいな話。で、『あ…。シンボリックリンクを張っておくかRewriteRuleで読み替えちゃえばいいかな…』なんて考えていたら読み替えるべきファイル名が数百個もあった件について…みたいな事例。
 しかも、正規表現で美しく読み替えようにもそんなこと出来ませんよセンセイ…みたいな事例。

 …え?ない?
 …ソウデスカ…アリマセンカ…orz

 ここでは、「あー。あるある!」と大人の対応をした人に向けて、「RewriteMap」を簡単に使って解決する方法を書いておきたいと思います。


 例えば、たった一つのファイルを読み替えたいときはどうするか?を考えます。
 img/fileA.jpg を、img/fileB.jpg に読み替えるとしましょう。この場合はチョー簡単です。

 RewriteRule img/fileA.jpg img/fileB.jpg [L]

 とかやっておけば事足ります。ところが、これを何百個も書きたくない訳です。このような場合には、「RewriteMap」ディレクティブを使います。手順としてはこんな感じです。

手順①:旧ファイル名 → 新ファイル名の読み替え一覧をテキストファイルで作成する
手順②:手順①で作成したテキストファイルをdbm型式に変換する
手順③:手順②のファイルを、「RewriteMap」ディレクティブで読み込み、条件を設定してRewriteRuleで読み替える


手順①:旧ファイル名 → 新ファイル名の読み替え一覧をテキストファイルで作成する

 まず、新旧ファイル名の読み替えをする一覧をテキストファイルに作成します。フォーマットは、「旧ファイル名」と「新ファイル名」とを1行に記述します。半角スペースやタブで区切ります。

img/fileA.jpg    img/fileB.jpg
img/fileC.jpg    img/fileX.jpg
img/fileD.jpg    img/fileY.jpg
img/fileE.jpg    img/fileZ.jpg


 なお、半角「#」がコメントとして使用可能です。


手順②:手順①で作成したテキストファイルをdbm型式に変換する
 次に、手順①で作成したテキストファイルをdbm型式に変換します。実のところ、変換しなくてもいけるんですが、読み替えるべきファイルが多い場合は変換した方がサーバ負荷が下がってオススメです。
 コマンドとしては、httxt2dbmコマンドを使用します。

 httxt2dbm -i (入力ファイル名) -o (出力ファイル名)

 です。入力ファイルには手順①で作成したファイル名を指定します。出力ファイル名にはdbm型式のファイル名をそれっぽい名前で指定すればよいでしょう。
 このコマンドを実行すると、拡張子「.dir」というファイルと「.pag」というファイルが作成されます。


手順③:手順②のファイルを、「RewriteMap」ディレクティブで読み込み、条件を設定してRewriteRuleで読み替える

 次に、httpd.confを調製します。
 まず、手順②で指定したdbm型式のファイルをRewriteMapディレクティブで読み込みます。

 RewriteMap imgurl dbm:/usr/local/apache2/conf/yomikaelist

 imgurlは単なる識別子です。次のRewriteRuleディレクティブで使用しますが名前は何でも構いません。
 dbm:/usr/local/apache2/conf/yomikaelistは、「dbm:」に続いて手順②で作成したファイル名を指定します。拡張子は必要ありません。

 で、これをRewriteRuleディレクティブで実際に読み替えます。

 RewruteRule ^/?(img/.*\.(gif|jpe?g)) /${imgurl:$1|$1} [L]

 これくらいの正規表現はちゃっちゃと読めないとねー。(笑)
 まず、RewruteRuleディレクティブの最初のパラメータ^/?(img/.*\.(gif|jpe?g))は、「img/」ディレクトリの下にある「ナントカ.gif」「ナントカ.jpg」「ナントカ.jpeg」を読み替えたいのでこのような記述をしています。ここで、丸括弧が2組出ているのが「なんじゃこりゃ」の原因になっていると思いますので解説しておきます。
 RewriteRuleの過去の記事にもしれっと記述してたりするんですが、丸括弧で囲んだ範囲はRewriteRuleディレクティブの内部的な「変数」に格納することが出来ます。(img/.*\.(gif|jpe?g))の外側の部分はそのための丸括弧で、丸括弧の中身は「$1」という変数に格納されます。
 内側の丸括弧部分(gif|jpe?g)/font>は拡張子「gif」または「jpe?g」のどちらかという記号「|」の範囲を特定するための物です。

 で、この正規表現によって、次のようなリクエストがあったなら、赤文字の部分が変数$1に格納されることとなります。

 http://server/img/fileA.jpg

 さて。この赤文字部分。そういえば手順①で作成したファイルの中にありましたよね?

img/fileA.jpg    img/fileB.jpg
img/fileC.jpg    img/fileX.jpg
img/fileD.jpg    img/fileY.jpg
img/fileE.jpg    img/fileZ.jpg


 この変数$1の内容をRewriteMapディレクティブで読み込んだアレに渡すと、見事img/fileB.jpgに化けて出てくる…という処理をしているのが、RewriteRuleディレクティブの二つ目の引数である /${imgurl:$1|$1} です。

 ${(RewriteMapの識別子名):(読み替え前の名前)}

 としておくと、読み替え前の名前を読み替えた後の名前が取得できます。つまり、

 ${imgurl:img/fileA.jpg}

 という指定をすれば、この変数は結果的にimg/fileB.jpgという値になって機能するということなのです。

 ただし、ここで一つ注意すべきポイントがあります。それは…

 読み替え「前」の名前が無い場合は、空っぽの値が返ってくる

 という点です。つまり、

 ${imgurl:img/fileAHO.jpg}

 という指定をした場合、この結果は「」という状態になってしまいます。このため、RewriteMapで読み込んだファイルの中に記述が無い場合は、RewriteRuleが「」に読み替えようとしてしまいますので非常に都合が悪いのです。

#そもそも、「読み替えたいファイルの一覧」があるということは、
#その中に「無い」ファイルは「読み替えて欲しくない
#と解釈すべきでしょ?

 と言うわけで、imgurlに値が「ない」場合の振る舞いも一緒に指定しておく必要があります。そのための記述が、「|$1」の部分なのです。

 ${imgurl:img/fileAHO.jpg|img/fileAHO.jpg}

 という指定をすると、imgurlに「img/fileAHO.jpg」があれば、読み替えたその文字列を返す。(「|」の前の部分)
 しかし、imgurlで読み替えた結果が「空っぽ」だった場合は「img/fileAHO.jpg」を返す。(「|」の後ろの部分)

 という処理を行うことになります。
 実際には、RewriteRuleで使用している変数「$1」の中身に応じて読み替えをしたいので、結果的に/${imgurl:$1|$1}という指定になっている訳です。



 これで、ファイルが読み替えるべき対象であるならファイル名を読み替え、そうでない(リストに無い)場合はそのままの名前でアクセスを通す…という事が出来るようになります。

EC2 SpotInstanceを使う [Amazon AWS]

Amazon EC2 のSpotInstanceは割とお安い訳ですよ。例えば、c3.2xlargeなら、定価0.511ドル/時のところ、SpotInstance価格ではなんと0.108ドル/時!!(この記事かいてる時点での価格)金利手数料はジャp(チガウ

SpotInstanceの仕組みとしては、Amazonさんの余剰リソースを安く使わせてもらおうというコンセプトのようで、その時のAmazonさんのリソースの空き具合に応じて「時価」が変動しますが、その時価よりも高い「スポット価格」を提示(入札)しているインスタンスに限って、リソースを使用してインスタンスを実行できるというもの。
例えば、今0.108ドル/時なので、「スポット価格」として0.2ドル/時を提示していれば、そのインスタンスは実行できるという感じ。
時折その時の「時価」がボーンと跳ね上がることがありますので、永続的に使用したいなーっていうのには向かないみたいですね。
一時的なバッチ処理をしたいとか、開発テストに使いたいとかそういう用途なら結構イケそうです。(Amazonさんもそう言ってますし。)

で、それを使ってみたいじゃないですか。コマンドラインから

という訳で試してみました。

スポットインスタンスはシャットダウン等するとインスタンス自体がTerminatedになってしまう模様(違ったらすんません)なので、1からインスタンスを立てて使おうという用途よりも、すでにあるインスタンスをスポットインスタンスで使うという感じが向いているのでしょうか。(違ったらすんません)

そんな訳でここでは「既存のEC2インスタンスからAMIを作成して、それをスポットインスタンスとして起動する」という作戦で実験したいと思います。

手順としてはこんな感じ。

① 既存のインスタンスからAMIを作成する
② (AMIの作成には時間がかかるので完成するのを待つ)
③ スポットインスタンスをリクエストする
④ (スポットリクエストが「履行中」のステータスになるのを待つ)
⑤ 必要に応じてタグを付けたりする



① 既存のインスタンスからAMIを作成する

 まず、既存のインスタンスからAMIを作成します。使用するコマンドは aws ec2 create-imageです。先に、既存のインスタンスのインスタンスIDを調べておく必要があります。

 aws ec2 create-image --instance-id (複製元となるインスタンスID) --name (AMIに付ける名前) --description (AMIに付ける説明文) --no-reboot

 「--no-reboot」オプションを省略すると、複製元となるインスタンスが強制的にリブートされます。問答無用です。このため、業務で使用中のインスタンスを指定した時などは悲しい思いをするかも知れないので要注意です。
 今のところ「--no-reboot」を付けたせいで問題になったということは経験していませんが、もしかしたら問題が発生するかもしれない?的な覚悟は一応しておいてください。(AmazonLinux以外の場合は特に…。)

 awsコマンドの実行結果は標準出力にjson型式で表示されますので、コイツを jqコマンドでパースしてやれば、AMIのIDが取得できます。取得すべき項目名は「.ImageId」です。


② (AMIの作成には時間がかかるので完成するのを待つ)

 AMIの作成には割と時間がかかりますね。ストレージの大きさにもよりますが。AmazonLinuxの標準(?)サイズの8GBでも数分はかかります。長いと10分以上要する事もあったりなかったり…。

 コマンドラインから完成したかどうかを確認したい場合は、aws ec2 describe-imagesコマンドで確認できます。

 aws ec2 describe-images --image-ids (AMIのID)

 作成が完了すると、「.Images[].State」に「available」が入ります。


③ スポットインスタンスをリクエストする

 はい。この記事の本丸です。(笑)
 スポットインスタンスをリクエストします。スポットインスタンスをリクエストすると、「リクエスト」が生成され、それにインスタンスが紐付く形になります。複数のインスタンスを同時に起動することも出来ますが、その場合は1個のインスタンスに1個のリクエストが関連付く格好になるので、スポットリクエストも複数作成されることとなります。スクリプトで処理する場合は配列処理するなり、ループ処理するなりしてください。

 使用するコマンドはaws ec2 request-spot-instancesです。
 ひとまず必要最低限のオプションとしてはこんな感じです。
 aws ec2 request-spot-instances --spot-price (提示する入札価格) --launch-specification (インスタンスをローンチするための情報)

 「提示する入札価格」は、お好みでどうぞ。単位はドル/時っぽいです。
 「インスタンスをローンチするための情報」には、最低でも次のような情報を指定することになります。
{
  "ImageId": "(AMIのID)",
  "InstanceType": "(インスタンスの種類)",
  "IamInstanceProfile": {
    "Arn": "(AMIインスタンスロールのArn)"
    },
  "SecurityGroupIds": ["(セキュリティグループID)"]
}


 VPCの中に入れたい時などは、VPCIDとかサブネットIDとかそういった情報も必要になってくると思います。(まだVPCの中に入れてテストしていないので…^^;)

 インスタンスをローンチするための情報をjson型式でファイルに書き出したら、file:///tmp/launch.json とかそんな感じで指定してコマンドに渡してやります。

 このコマンドを実行すると、実行結果がこれまたjson型式で返ってきますので、これまたjqコマンドで適宜パースしてやります。なお、「リクエストID」がこの後に必要となりますから、保存することをお忘れ無く。(リクエストIDは「.SpotInstanceRequests[].SpotInstanceRequestId」という項目に入ってきます。)


④ (スポットリクエストが「履行中」のステータスになるのを待つ)
 スポットインスタンスを生成した直後はまだインスタンスは存在しません。
 スポットリクエストが「履行中」というステータスになるのを待つ必要があります。確認するコマンドはaws ec2 describe-spot-instance-requestsです。コマンドラインで特定のリクエストIDだけ表示することが出来ないっぽいので、jqコマンドで必要な項目だけselectしてやる必要があります。
 おそらくこんな感じ。

aws ec2 describe-spot-instance-requests | jq '.SpotInstanceRequests[] | select ( .SpotInstanceRequestId == "(スポットインスタンスのリクエストID)" ) | .Status.Code'

 「.SpotInstanceRequests[].Status.Code」という項目にスポットインスタンスの状態が入っています。こいつが「fulfilled」になると、インスタンスが使用可能な状態となっていることを表します。
 また、スポットインスタンスの状態が「fulfilled」になった時、そのリクエストの「.SpotInstanceRequests[] .InstanceId」という項目を見ると、このリクエストに紐付いているEC2インスタンスのインスタンスIDが判明します。


⑤ 必要に応じてタグを付けたりする
 当たり前のように、ローンチされたばかりのEC2インスタンスにはタグも何も無いので(Nameタグすらない!!)、適宜タグを付けてあげましょう。



なお、注意点としては…
・インスタンスをshutdownするとインスタンスがterminatedになるっぽい
・スポットインスタンスの上限は20個まで(インスタンスサイズによっては10とかいう場合も有る)
・必ずしも「定価」より安いとは限らない模様
・Amazonさんの余剰リソースの状況によっては突然terminatedになる恐れがあるらしい

スポットインスタンスの上限を拡大してもらえるのかどうかは不明です。申請フォームには無かったようですが…??(私の目が節穴である可能性に注意)

それにして、安いよねー。

トラフィック課金は高いけどなー。
メッセージを送る