続 カッコの付け方

AWSを始めとしたクラウドコンピューティング全般と、唯一神emacsにおける()の付け方についてだらだら書きます

CloudFront ワイルドカードキャッシュクリアの不具合(修正済)

CloudFront(以下CFと略)を始め、CDNを利用するにあたり、まず注意することは、キャッシュを削除する手段を確立することです。CFはinvalidationをURL毎に発行することにより、このキャッシュクリアを実現していましたが、昨年、ワイルドカード (e.g /image/*) で一気にURLを指定し、クリアすることができるようになりました、が、一部の環境において不具合があり、それをAWSサポートに連絡、修正してもらいました。今はすでに治っているので問題ありませんが、CFの理解のために記述しておきます。

不具合ってどこよ?

https://blog.cloudpack.jp/2014/11/06/etcetera-to-update-header-on-cloudfront/

この問題を 勝手に古いヘッダのキャッシュ問題 と呼称します。
発生のメカニズムは

  1. CFにキャッシュされる、このとき、レスポンスヘッダもキャッシュされる
  2. TTLが切れて、オリジンにリクエストが飛ぶ
  3. オリジンのヘッダだけが変わっていて、コンテンツに変化がない場合、オリジンは304を返す
  4. CFは304を受けると、1. でキャッシュしたデータをつかいまわす

これに対して invalidationをかければOK!なんですが、ワイルドカードでinvalidationを掛けた場合、上手くクリアできません。

ヘッダだけが変わるってどういう状況?

CORSです。CORSの設定を忘れて、CFにキャッシュされたあとに、オリジンにCORS向けヘッダを指定したらこの状況になります。

CORSってなんや?

長くなるので適当に AJAXなどで、動的に別のURLの画像やコンテンツを取得し、使う場合に発動するブラウザ側のセキュリティーチェックと考えてください。
「えっ、Yahooとかでも外部サイトの広告とか出てるやん」と思うでしょうが、あくまでも動的にやる場合です、HTMLにベタでimageタグ書く場合はCORS関係ないです。ちょー適当にいうとWeb2.0です。知らないと生きていけない知識ですので、はじめて知った人はググってください。

今回はGETのことだけ考えていますが、POSTの場合でもCORSはあります。プリフライトリクエストでググってください。こちらはちょっとむずかしいですので、私のボケ防止にいつか書く。

CFのキャッシュの一生

CFは特定のURLリクエストを受けると、下記の動作をとります。

  • CFにキャッシュが無い場合は、オリジンに取りに行く Miss from cloudfront
  • CFにキャッシュがあり、且つTTLが切れていない場合は、キャッシュから返す Hit from cloudfront
  • CFにキャッシュがあるが、TTLが切れている場合は、再度オリジンに取りに行く RefreshHit from cloudfront

上の2つはだいたいわかると思いますが、最後の RefreshHit from cloudfront の時に 古いヘッダのキャッシュ問題は発生します。

TTLが切れたらキャッシュは破棄されるのではないのか?

破棄されません。破棄されるとおもいますよねーふつう。原理的にこれがあるので、古いヘッダのキャッシュ問題が発生するのです。

ということは 古いヘッダのキャッシュ問題は放っておいたらずっと解消しない?

確率としては極めて低いですが、解消する可能性はあります。
CFは RefreshHit from cloudfront 状態で実はキャッシュは捨てていないのですが、キャッシュ対象のURLに一定時間以上、アクセスが発生しない場合は、完全にキャッシュ削除されるタイミングがあります。しかしこのタイミングは完全にAWS様の機嫌次第です。何分とか明言されておりません。またTTLが1日とかの場合は挙動が変わるかもしれません(試してないです)

検証

状況を整理しておきます。

  • 古いヘッダのキャッシュ問題そのものは修正されていない。個人的にはTTL切れ = キャッシュ破棄としてもらうほうがいいので、改善要望は出した。
  • 古いヘッダのキャッシュ問題のW/Aは、invalidationすること。
  • しかし 個別URLで invalidation した場合は綺麗に Miss from cloudfrontになるが、ワイルドカードでinvalidation した場合は、RefreshHit from cloudfrontになる(キャッシュ破棄が発生しない)。

今回の問題は、ワイルドカード invaliだと古いヘッダのキャッシュ問題のW/Aが効かない(かった)、ということです。しかもこの問題はTTLが十分に長い設定(e.g 1day)などの場合は発生しません。私の検証では、Hit from cloudfront状態つまり、TTLが切れていない状態で、ワイルドカード invaliを掛けた場合は、なぜかMiss from cloudfrontになりますつまりW/Aが効いて、キャッシュが破棄される。TTLを1分とか短い設定にしていると、必然的にRefreshHit from cloudfrontになりやすく、この状態でのW/A(ワイルドカードでinvaliをかける)が効かないということがわかっています(た)。

手順概要

  1. S3にダミーコンテンツをぶち込む
  2. S3にCORSの設定をしない
  3. CFをS3オリジンで作る、TTLは30秒とか短くする
  4. CFにキャッシュを吸わせる = 古いヘッダのキャッシュ を作る
  5. S3にCORSの設定をする
  6. CFに問い合わせて古いヘッダのキャッシュ問題の発生を確認
  7. ワイルドカードで invaliかける
  8. CFに問い合わせてMiss from cloudfrontを確認 (不具合修正の確認)

検証スクリプト

S3にダミーコンテンツをぶち込むやつ

#!/bin/env ruby

require 'aws-sdk'
BACKET_NAME = 'YOUR Bucket Name'

def put_3k_files (d, ver)
  s3 = Aws::S3::Client.new(region: 'YOUR Region')
  for num in 0..1000 do
    key = d + sprintf("/%04d.txt",num)
    s3.put_object(bucket: BACKET_NAME, key: key, body: 'this is ' + key + 'ver ' + ver)
  end
end

put_3k_files 'js', '1'
put_3k_files 'js/001', '1'
put_3k_files 'js/001/a', '1'
put_3k_files 'js/001/b', '1'
put_3k_files 'sound/prd', '1'
put_3k_files 'asset', '1'

S3を突っつくやつ

#!/bin/env ruby

require 'net/http'
require 'uri'
require 'resolv'

def get_url(ac_url)
  url = URI.parse(ac_url)
  req = Net::HTTP::Get.new(url.path)
  req.add_field 'Origin', 'http:example.com'
  req.add_field 'Host', 'CF FQDN'
  res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req)}

  ret_arr = [7]
  ret_arr[0] = ac_url
  ret_arr[1] = res.code
  ret_arr[2] = "None"
  res.each do |n, v|
    if n == 'access-control-allow-origin'
      ret_arr[2] = v
    end
    if n == 'content-length'
      ret_arr[3] = v
    end
    if n == 'x-cache'
      ret_arr[4] = v
    end
    if n == 'etag'
      ret_arr[5] = v
    end
    if n == 'age'
      ret_arr[6] = v
    end
  end
  return ret_arr.join("\t")
end

rslv = Resolv::DNS.new()
ip_list = rslv.getaddresses('CF FQDN')

dirs = ['js','js/001','js/001/a','js/001/b','sound/prd','asset']
#dirs = ['js']

for num in 0..1000 do
  key = ARGV[0] + sprintf("/%04d.txt",num)
  full_url = 'http://' + 'CF FQDN' + "/" +  key
  print get_url(full_url) + "\n"

#    ip_list.each do |ip|
#      full_url = 'http://' + ip.to_s + "/" +  key
#      print get_url(full_url) + "\n"
#    end
end

コードは散らかっているが、引数に js とか asset とか指定すると、その配下の1ファイルを突っつきます。出力の3カラム目に *がきていれば CORS用のヘッダが来ています。コメントアウトしてますが、DNSラウンドロビンで返してくるIP全てに対しても打つ事ができます。(あたりまえですが、めちゃ時間かかります)興味ある人は試してみてください。

検証結果

問題は確かに解消していました。

まとめ

ワイルドカードによるinvalidationの問題は解消されましたが、基本的にinvalidation自体が悪手です。さらに、前述してますが、TTLが切れたらキャッシュも削除される という認識が根本的に間違っています(いいか悪いかを別にして、CFにおいては)。さらにさらに、TTLさえ短ければキャッシュ残りは心配しなくていいという考えも間違っています、レスポンスヘッダとコンテンツをCFはごちゃまぜでキャッシュしています。
よって、古典に習い

  • 先頭URLをバージョンとかにして、バージョンごとにURLを使い捨てる
  • QueryString等でごまかす

を使うのがベストです。別URLはCFは別キャッシュだと判断するので、オリジンが同じであろうと関係なく、URLが違えば別キャッシュとして扱う と覚えておきましょう。ちなみにリクエストヘッダもCFが同じキャッシュとみなすか否かに含まれているようです(試してみてください)。少し話がそれましたが、invalidationはおまけぐらいに心構えるのが良いです。

.. あと、この手の検証をやる人は、ブラウザ側のキャッシュを強制無効にしてやるのがいいです。

Google Cloud SQL 2nd GenerationのFailOverなど

GAEのために出来た、という歴史的な背景からもあまりGCEから使うこともなかった Cloud SQL (1st generation)ですが、昨年 2nd generation がベータとなりました。パフォーマンスの高さについてはアナウンスされていますが、この記事はFailOverを中心に、2nd generationを探っていきます。

Cloud SQLとは

AWSで言うところのRDS、マネージドSQL(RDB もっと言うと MySQL)です。VMインスタンスに自前でMySQLを入れるよりも勿論割高ですが、バックアップとかフェイルオーバとかの面倒をGoogleが見てくれるというものです。

最初に結論

  • アクセスIPアドレスは、相変わらず Global IPのみ
  • アクセス元IPアドレスで制限
  • GCEの内部ネットワークに作成は出来ないが、GCE内から接続する分には、ネットワーク速度的なペナルティーはない(多分)
  • RDSのMulti-AZに相当する機能はある、が、そもそもGCEはZone (AZ) がどれ位離れているかとか明言されていない
  • Multi-AZと書いたが、Active/Standbyではない、Active/Active(RO) なので、どちらかと言うと AWS Auroraに近い (語弊を生む乱暴な言い方だが、構成としては)
  • FailOverの機能はある、が、自動的にエンドポイントを書き換える機能は無い
  • レプリケーション構成(semisync)

構成と役割

f:id:iga-ninja:20160207132316p:plain

1つのDBクラスタとして管理します。各ロールについて

  • マスタサーバ
    文字通り
  • フェイルオーバレプリカ(RO)
    レプリカですが、フェイルオーバ時にマスタ昇格の対象となるものです。 必ずマスタサーバと同じインスタンスタイプとなります

  • リードレプリカ(RO)
    文字通りのレプリカ、マスタが死んでもこいつはマスタ昇格しない。マスタサーバと異なるインスタンスタイプでもOKです

フェイルオーバレプリカ はAuroraのReaderに近い存在です。それに加えて、絶対にマスタ(Writer)に昇格しないノードがリードレプリカです。

実践

Developers Consoleからポチポチしていれば出来ます。が、注意点のみ記載します

1) GCEからの接続前提であれば、勿論リージョンをあわせること

2) マスタのバックアップを取らないと、レプリカが作れない これは フェイルオーバレプリカでもリードレプリカでも同じ

f:id:iga-ninja:20160207133312p:plain

接続確認

mysqlコマンドで接続できます。初期ユーザは root & パス無しなので、とっととパスワードを指定しましょう。

レプリケーションの様子

  • マスターノード
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000006 |      120 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.08 sec)

mysql> select @@binlog_format;
+-----------------+
| @@binlog_format |
+-----------------+
| ROW             |
+-----------------+
1 row in set (0.09 sec)
  • フェイルオーバレプリカ
mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: [master global ip]
                  Master_User: cloudsqlreplica
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 120
               Relay_Log_File: relay-log.000005
                Relay_Log_Pos: 283
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 613
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: Yes
           Master_SSL_CA_File: master_server_ca.pem
           Master_SSL_CA_Path: /mysql/datadir
              Master_SSL_Cert: replica_cert.pem
            Master_SSL_Cipher:
               Master_SSL_Key: replica_pkey.pem
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: [master server id]
                  Master_UUID: 5c40936b-cca9-11e5-8416-0242ac110009
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
1 row in set (0.14 sec)

mysql> select @@read_only;
+-------------+
| @@read_only |
+-------------+
|           1 |
+-------------+
1 row in set (0.12 sec)
  • リードレプリカ
# show slave statusはほぼ同じ

mysql> select @@read_only;
+-------------+
| @@read_only |
+-------------+
|           1 |
+-------------+
1 row in set (0.12 sec)

FailOver

2016/02/06現在、gcloiudコマンドでもFailOver発動のコマンドはありません。ただし、Cloud SQL APIを叩けば発動出来ます。 https://cloud.google.com/sql/docs/high-availability#test

FailOver手動発動

curlで叩けと書いてありますが、ちょっとむずかしいので API Exprolerで簡単に打ちます。 まず、Cloud SQL API を有効化させます。

f:id:iga-ninja:20160207134812p:plain

ご丁寧に誘導リンクがあるので、そのままGo

f:id:iga-ninja:20160207134842p:plain

f:id:iga-ninja:20160207134904p:plain

上記のドキュメント通りに打つのですが、SettingsVersion ってなんやねん! ということで、これを先に取得します。gcloudで取得可能

gcloud sql instances describe <your master instance-name>

....
  kind: sql#settings
  locationPreference:
    kind: sql#locationPreference
  pricingPlan: PER_USE
  replicationType: SYNCHRONOUS
  settingsVersion: '19'
  tier: db-g1-small
state: RUNNABLE

この場合だと 19です。
これで実行可能です

f:id:iga-ninja:20160207134913p:plain

Failoverの挙動

こんな感じです

  1. マスター止まる
  2. フェイルオーバレプリカが昇格
  3. 元マスター復帰
  4. 元マスタ-がマスターに昇格、同時にフェイルオーバレプリカダウン
  5. フェイルオーバレプリカがスレーブに戻る

AWSとは異なり、DNSに依存しないIPベースと言うポリシーは、悪い意味でも一貫しています。あくまで、意図的にフェイルオーバを起こした場合の動きですが、実際の障害時も同じ動作になると思います。
特筆することは、マスターのIPやエンドポイントが一定ではないという点です。上記パターンでもマスターは マスター -> スレーブ -> マスターと切り替わっていますが、エンドポイント(IP)はそれを追従していません。文字通りDBのフェイルオーバのみが実行されています。
この点をどう扱う(える)かが1つの導入への障害となりえます。フェイルオーバの一連の作業の中で、各サーバのマスタ昇格のイベントを捕まえることが出来ないので、HAPoxyの設定を変えてエンドポイントを変更したり、consulとかも難しいと思います。
ただ、これはエンジニアの勘ですが、最近この手のエンドポイント問題は、クライアントライブラリ側で吸収するのが主流となっていく気がします(mongoを除く) 。すでにPHPでもmysqlnd-msなど Read/Write spplitingを含めてHA構成のエンドポイント探しは、ライブラリ側の方が柔軟でかつ賢い実装がすでに出揃いつつあると感じています。

あとから変えられる?

1st に比べてかなりの部分が Pre-Provisionな感じになりましたが。。

ディスク

増やせますが、減らせません。1st-genの時は完全オンデマンドだったので、そちらも選べればよかったのですが。10 -> 15GBしか試していませんが - ダウンタイムはゼロ - ディスク拡張中のパフォーマンスは言及されていない

なお、容量アップにかかる時間は、恐らくもともとのディスク容量に依存するかは試していません。ただし、10 -> 15GB の場合は、一瞬でした。 ですが、問題があります!ここは後述

インスタンスタイプ

変更できます。が、もちろんサービス断有りです。所用時間は 2 - 4 分程度で、結構遅いな-

その他

以外なところではフラグ = サーバパラメータの変更でも再起動が必須な点でしょうか、下記ダイアログが出ます

f:id:iga-ninja:20160207162554p:plain

フェイルオーバレプリカと変更

ここが結構問題です。マスターのディスクをでかくしたら、勝手にスレーブ(フェイルオーバレプリカ)もデカくしてくれそうなもんですよね?
インスタンスタイプを変えたらスレーブも変わりそうなもんですよね?最初に一緒にしないとダメとか言うなら。
それが変わらないんですよ。

f:id:iga-ninja:20160207162533p:plain

ということは、マスターのスペックを変えたらフェイルオーバレプリカ作り直しということになります。これはイケてないなー
ちなみにこの点はドキュメントと矛盾しています。

After the failover replica is created, you can change all configuration settings of the instance, except that you can not enable backups or change the activation policy.

まとめ Pros/Cons

Pros

  • 1stから、DB限界データサイズが大幅UP
  • リードレプリカ台数調整可能
  • 自動FailOver
  • インスタンスタイプが明確GCEと同じ感覚で指定できる

Cons

  • 1stにあったデータ従量課金が無くなった
  • IP制限がGlobal IPのみなので、タグとかで制限出来ない、具体的にGCEでもエフェメラルIPを使っている場合は、Cloud SQL Proxyというのを使う必要がある。https://cloud.google.com/sql/docs/sql-proxy
  • 自動FailOverするが、エンドポイント書き換えはない
  • フェイルオーバレプリカのスペック変更ができない(ドキュメントはできるっぽいことかいてあるが、、)

Crate.IO クラウドネイティブな新しいデータベースエンジン

Crate.IOという新しいDBエンジンが 2014 TCのアワードをとっていたらしい。それからもう1年近く立っていますが、正直なところ、流行っているのか定かではないです。が、誰もやらないことのほうが面白いのでやります。

Crate.IOとは?

Crate でググると、木の箱の絵が出てきたり、Rustのパッケージマネジャ?とかぶるので、Crate.IOとします。正直、Crateはかなりググりにくい言葉です。ざっとまとめとくと

  • マルチマスタ型
  • ハナからクラウド前提
  • RDBで全部やるのじゃなくて、NoSQLでやろう、それもNoSQLの中でも用途ごとに分割しよう」という最近の風潮を全否定 全部 Crate.IOでやれ!
  • クライアントライブラリはもうほとんどの言語で用意済み
  • サーバ側はゼロコンフィグでいける(ようにしたい)

なかなか魅力的な おしながき でしょう?流行るかどうかはわからないですが。

アプリ開発者視点

Use Cases

インフラ的にいくら面白くとも、アプリ開発上意味がなければわざわざ採用する価値がありません。ましてや新しいDBエンジンです。全体的な印象としては、開発者に優しいと思います。
なんといっても
SQLが通る!

Crate SQL — Crate documentation

やっぱりSQLは強し! 上記マニュアル流し読み。

DDL

普通のRDBDDLとほぼ変わりませんが、シャーディング・ルーティングを意識した構文があります。

DDL デフォルトでシャーディング5本

Crate supports sharding natively, it even uses 5 shards by default if not further defined.

らしいです。5本以外だとDDLで指定すればOK。

Routing デフォルトはPrimary Key

If primary key constraints are defined, the routing column definition can be omitted as primary key columns are always used for routing by default.

Routingというと聞き慣れませんが、所謂シャードキーと捉えれば良さそう。Primary Key無しで、特定のカラムをシャードキー(clusterd by) に指定する。

Replication デフォルトで1レプリカ

Defining the number of replicas is done using the number_of_replicas property.

何箇所にレプリケーションするかを、DDLで指定しちゃいます!マニュアルによると最大4つ。ここまでやれば盤石でしょう。
しかもですよ!

Note The number of replicas can be changed at any time.

なんだと!

DDLまとめ

他にも Full Text や Partitioned Tableなど、ジジイがニヤつく機能やら、聞き慣れない Analyzer という物があったり、マニュアル読むだけでお腹いっぱい。
私が抜粋した分だけでまとめますと。

  • シャーディング・ルーティング・レプリケーション等、本来はウラ(インフラ側)で吸収していた部分が、思いっきりオモテ(=DDL)に出てきた

最初から、いかにスケールさせるかを想定して作られているので、これらの機能をDDLで指定します。ひとことで言うと、 なんかMongoDBっぽい ですが、MongoDBよりももう一歩踏み込んでいる感じはします。インフラ(サーバ)側を知るとそれは明確になります。

Query

https://crate.io/docs/reference/sql/queries.html#object-arrays

基本的なSQLは通りそうですが、その中でも気になったのが、Arrayに対するQueryです。いずれ試してみる。

Types

Object型など、ある程度予想がつくものもありますが、ユースケースにも上げられているジオ系のTypeと関数があります。複雑なことは出来ないですが、lat-longから距離を測ったりとかはできるようです。もっと複雑な機能は自分で拡張しろやってことでしょう。

https://crate.io/docs/reference/sql/data_types.html#geo-point

https://crate.io/docs/reference/sql/scalar.html#geo-functions

Blob

https://crate.io/docs/reference/blob.html

blobテーブルはDDLで定義して、Upload & Download は HTTP経由ですね。ここのDDLも勿論 シャード、レプリケーション数が指定できるようです。

各種言語の対応状況

安心してください、ほぼ全部です。

https://crate.io/docs/getting-started/clients/

万が一見つからなかったとしても。HTTPのRESTを叩くだけっぽいので、多分移植は楽勝。

アプリ側のまとめ

実のところ、インフラ側の方が楽勝ヒャッハーなんで、そこを書こうと思ってましたが力尽きました、次回書きます。
アプリ側も改めて見ると結構いいですね~

  • SQLが使えるよ!
  • シャーディング・ルーティング・レプリケーション、これらすべてDDLで指定する
  • Blobも使えるよ!、Upload/DownloadはHTTP
  • 認証機構は今のところ無い(マニュアル読んだ分だと)

今後Crate.IOの主戦場では、おそらく認証機構は必要となりそう。認証を入れた上で、どれぐらいのスピードが確保できるか。
最近の動向として、ESとの連携(データのマイグレーション?)が話題になっているようです。ココらへんも後ほど書くつもり。

知らん間にGoogle Cloud loggingでログ監視ができるようになっている件

GCPあるある
久しぶりにDevelopers Consoleにログインして新機能実装に気づく。

一応RSSとか購読してるんですが、ほんとにこのパターン多い。今回はCloud Logging (& Cloud Monitoring)です。以前にLog監視できたらいいなーとか、Pub/Sub + GAE でログ監視ができる!と言ってたのですが、ノンプログラミングでできるようになりました。

仕組み

Cloud Logging も Cloud Monitoring も本ブログで触れています。Cloud Logging は AWSCloud Watch Logs に似た仕組みですが、Cloud Logging自体にはLog監視機能がありませんでした。今回の機能追加でかなりの部分が競合するシステムとなりました。
今回のログ監視の仕組みもほぼ、AWSCloud Watch Logs と同じです。Logs-Based Metrics という名前そのまんまの機能を使って、Logをメトリックスとして Cloud Monitoring に見てもらいます。(グラフを書いたり、アラートを飛ばしたり)
今回はカスタムログ(自前のログ)を用いないので、ちょっと違いますが、だいたいこんな感じ。

Google fluent on GCE -> Cloud Logging -> Logs-based Metrics -> Cloud Monitoring -> アラート発報

実装

簡単です!今回はログの流し込みはやらないで、Activity log という、GCEのAPIコールのログを使います。

Cloud Logging側

f:id:iga-ninja:20151025091144p:plain

f:id:iga-ninja:20151025091323p:plain

f:id:iga-ninja:20151025091333p:plain

f:id:iga-ninja:20151025091340p:plain

Cloud Monitorninng側

f:id:iga-ninja:20151025091425p:plain

f:id:iga-ninja:20151025091441p:plain

参考

Logs-based Metrics - Cloud Logging — Google Cloud Platform

unicast対応でよみがえる keepalived on VPC-EC2

AWSを始め、ほとんどのパブリッククラウドはmulticastが出来なくて不便。と言われてきましたが、だったらunicastでやってしまえば良い!という考え方が 2013年頃すでにありました。なぜmulticastをやりたいの?と言うと そうだね!、LVS + keepalived だね! (あとはゴシッププロトコル) これまた2年前に EC2 + LVM (DSR) を実践してみたという素敵な解説があり、パッチを当てればいける ということは知ってたけど、月日がながれて 今日keepalivedの総本山を見てみたら

http://www.keepalived.org/changelog.html
Release 1.2.8 ぐらいからunicast対応してますね。というか、今更開発が活発になってきたのか?これは。というわけで、最新版の 1.2.19を使ってみます。

予備知識

EC2でmulticast でググると日本語を含めて結構な情報が得られます。混乱しないよう先に言うと、keepalivedのVRRPだけ に限ると、パケットキャプチャやらtcやらで自前実装して、事前準備するということは不要です。keepalivedがVRRPをunicastで実現してくれます 上記でもちらっと匂わせていた話ですが、

www.slideshare.net

は、汎用的な話です。つまり素直にmulticast/broadcastをエミュレーションしようということです。

www.slideshare.net

は、keepalived固有の話です。ていうか手順。keepalived側ですでにVRRP via unicastを実現しているので、パケットを覗いてMACアドレス分コピーして再送とかを自前で実装する必要はないです。

構成

LVS * 2 で VRRP冗長
Web * 2
動作確認用 Client
というこれまたパクリ構成です。

  • LVSの2つはお互いにVRRPで生きていることを広告します(正確にはVRRPで投げるのはMasterのみ、死亡を確認するとBackupが昇格)。重要な要素はVIP(フローティングIP)の存在です。このVIPを2台のうちどちらが持つかを決定します。
  • さらにこのVIPは EIP/ENIとは違います。この方法はEIP/ENIのインスタンスへの付替えは発生しません。大事なことなので2度言いますEIP/ENIの付替えは行いません。 しかし、ルートテーブルのみ書換は行います。詳細は後述。
  • VIPという(VPCから見ると)異物で一旦リクエストを受けるので、Src/Destのチェック無効化LVS側にも必要です。
  • Webサーバの死活監視もLVS(keepalived)で行います。

Webサーバ側は、DSRに備えて Src/Dest のチェック無効化VIP宛のパケットを、自分宛てとみなして受け取る設定が必要です。これも後述。

keepalivedの実装

上記より、インストールさえ済めば、keepalived.confの設定のみで、VRRPの部分は終わりです。そのたDSRを実現する部分やらは Sugawara氏のスライド丸パクリです。

インストール

RPMを作りました、AmazonLinux用のみです。気が向いたらCentOS用も作ります。
http://bit.ly/1OZfBtE
でもSRPMがあれば簡単なので、自力でなんとかなると思います。keepalivedぐらいでしたら、素直にソースビルドでもよいかと。

keepalived.conf

Sugawara氏のスライドと違うところは

vrrp_unicast_bind => unicast_src_ip
vrrp_unicast_peer => unicast_peer { xxx } 配列になった!

ぐらいです。

!Configuration File for keepalived

global_defs {
  router_id LVS_DEVEL
}

vrrp_instance VI_1 {
  state BACKUP # or MASTER
  interface eth0
  virtual_router_id 51
  priority 100 # MASTERは 101
  advert_int 1
  authentication {
    auth_type PASS
    auth_pass 1111
  }
  virtual_ipaddress {
    192.168.20.11
  }
  unicast_src_ip 172.31.18.163
  unicast_peer {
    172.31.18.164
  }
  notify_master "/etc/keepalived/notify_master.sh"
}

virtual_server_group VSG_1 {
 192.168.20.11 80
}

virtual_server group VSG_1 {
  delay_loop 6
  lb_algo rr
  lb_kind DR
  protocol TCP

  real_server 172.31.8.1 80 {
    weight 1
    HTTP_GET {
      url {
        path /
      }
    }
  }
  ...
}

切替時に走る スクリプト notify_master.sh EC2-Roleでやったので、credentialのベタ書きはやめました。ここはおこのみで。

#!/bin/bash

VIP=192.168.20.11
ROUTE_TABLE_ID=rtb-[your routetable's id]
INSTANCE_ID=`curl -s 169.254.169.254/latest/meta-data/instance-id`

#export AWS_ACCESS_KEY_ID=...
#export AWS_SECRET_ACCESS_KEY=...
#export AWS_DEFAULT_REGION=ap-northeast-1

aws --region ap-northeast-1 ec2 delete-route --destination-cidr-block $VIP/32 --route-table-id $ROUTE_TABLE_ID
aws --region ap-northeast-1 ec2 create-route --destination-cidr-block $VIP/32 --route-table-id $ROUTE_TABLE_ID --instance-id $INSTANCE_ID

VRRP動作確認

tcpdum -n vrrp を叩いて、multicast用IPアドレスが出ず、上記で指定したローカルIPアドレスが出ていればOK。他にも
ip a を叩いて VIPをeth0が持っていないことを確認します。2台ともVIPを持っていたら、スプリットブレイン状態です。多分設定ファイルかSG設定が間違っています

VIPの切り替わりとRouteTable

結構複雑な話が混ざっているので、1つずつひも解きます。

  • VIPのこだわり
    VIPは必ずVPCのCIDRから外れたものを指定します。後で意味が分かります。 具体的にはクラスAを割り当てていれば 172.16.xxx.xxx とか 192.168.xxx.xxx クラスの違うローカルIPを当てるのが良いです。
  • RouteTableが必要な理由
    VIPの面倒はVRRPが見てくれるなら、RouteTableなんか要らないんじゃないか?それにVIPもVPC(Subnet)内の空いてるIPさせば良いんじゃね?と思いますが、それは出来ないです。
    VPC内はそもそもブロードキャストが通りません、よって、MACアドレスの割り出しのためにARPを打っているわけではないのです。これにより、このVIPがわかっていてもそこに至る経路が分かりません。それをRouteTableで何とかします。
  • RouteTableのルール
    RouteTableで宛先を指定できるならば、VPCのCIDR内のIPでも良いんじゃね?と思いますが、これはやりたくても出来ません。実際にやればわかりますが、エラーが出ます。よって、VPCのCIDR外の適当なIPアドレスをVIPとして、これをRouteTableの経路とします。
  • notify_master.shスクリプトがやること
    VIPの割当はkeepalivedが勝手にやりますので、経路の変更のみです。ルートテーブルで、VIPへ向き先をMasterとなった自分(インスタンスID > ENI)に向けます。

バックエンドのWebサーバ側の設定

DSRで動かすことを意識した設定が必要です。
- Src/Destのチェックを無効にする
- iptables で VIP宛のパケットを自分宛てにする
点を注意

iptables

iptables -t nat -A PREROUTEING -d 192.168.20.11 -j REDIRECT

動作と解説

上手く動いていると、Clientマシンから curl 192.168.20.11 が正常に動作するはずです。この構成でできることを後述します。

どこでAZをまたいでも問題ない

  • Client - LVS(Master)間のAZ跨ぎ
    VIPへのアクセス経路はRouteTableに制御されています。よって、NATインスタンスと同じような感覚で使えます。
  • LVS間のAZ跨ぎ
    結局ただのunicastでVRRPを投げるので、AZを跨いでも全く問題ありません。keepalivedVRRPについては万事OK。
  • バックエンドサーバ - LVSのAZ跨ぎ
    問題ないのはわかると思いますが、Client(A) - LVS(C) - Server(A) と最悪の場合でも、DSRでいけるので、コネクション確立後は Client - Server の AZ-A内で通信ができるはずです。

まとめ

  • keepalivedの開発がなんか活性化してて、VRRP via unicastに対応。これにより keepalived = multicast必須でなくなった
  • VIPの付替えはkeepalivedがやってくれるけど、RouteTableの書き換えはAWS CLIでやる必要あり。EIP/ENI付替えよりはスマートだと思う。
  • AZ跨ぎも問題ない。

multicastについて

結局multicast関係無くなってしまいましたが、色々ためしてみました。
まだ途中ではありますが、パケットをキャプチャし、MACアドレス分コピー送信という方法はあまり上手くいっていないです。(私のテストが間違っているだけかもしれませんが)
別解として、GREトンネルを張る方法も下記にドキュメントがあります。いずれ試してみようと思っています。

Overlay Multicast in Amazon Virtual Private Cloud : Articles & Tutorials : Amazon Web Services

VPC Flowlogが見づらいので、htmlでViewer作ってみた

VPC flowlogという機能が最近リリースされました。SGを通過した・蹴られたのログが取れるというものです。これ自体はとてもありがたいのですが、ログの閲覧はCloudWathLogsからとなり、コレがイケてない。だから何とかしたという話

どんなふうにイケてないのか

CloudWatchLogs上の見え方ですが、こんなかんじです。 f:id:iga-ninja:20150816210033p:plain

フィルタで頑張ることも出来なくはないのです。たとえば80ポートへのアクセスで絞るなら[スペース]80[スペース]とかでフィルタかければなんとかいけます。ただ、Unixタイムスタンプはキツイよねー

で作りました

gist.github.com

f:id:iga-ninja:20150816210256p:plain

f:id:iga-ninja:20150816210238p:plain

機能解説

ローカル実行可能 Credential別サーバに投げるとかない

ローカルにおいたhtmlファイルを、ブラウザで叩いてOK。Web Storageとかも使ってない(使えてない)

ローカルファイルとしてJSONダウンロード可能

CreateDownloadLink を押してから 右のリンクをクリック。事前にデータロードした状態でないと動かない。おまけで付けた機能

各columnで絞り込み

80とかにマッチさせたければ、Regex on で ^80$とかしないとダメ。あんまりイケてない。

問題点

そんなに作りこんでいない。でかいデータをガツンとやるなら、素直にローカルにダウンロードするツール使った方がいい。

何度か実行すると Call Stackが足らんとか言われる

いつか直す。

4,5 万行ぐらいでまともに動かない

Dynatableの方がいいのかも?StartDateで絞れるようにはした。

各種部品(ライブラリ)

AWS SDK

S3は少々CORSとかで癖があるけど、それ以外は特に問題ない。必要な機能だけの最小版も作れるみたい。

Build your own AWS SDK for JavaScript

これいいね!

datatables

テーブルをいい感じに捌くやつ。調べつつ使ったけど、あんまり使いやすくない。

moment.js

jsの面倒くさいところに、日付の変換とかがありますが、こいつはいい感じに捌いてくれます。

bootstrap-datetimepicker

昔からよくあるやつだけど、こいつのお陰でmoment.jsに触れた。

File API

とりあえず使ってみたかっただけ。

感想

やっぱjs難しいな-と思った。Bootstrap以外のものを使ってみよかとおもったけど、やめた。ともかく非同期地獄をコントロールできるようにならねばな、Promiseとか。

ipv6とAWS 繋がるから、運用まで

世の中の全てのデバイスがipv6グローバルIPアドレスを持つ時代が来る。そんな時代が来ると考えていた頃が私にもありました。

f:id:iga-ninja:20150815105712p:plain

2015年になった今も、残念ながらipv4が主流のままです。また、足らないグローバルIPやら、本当はグローバルIP同士でやるべき通信をipv4で何とかしようとする技術は沢山あります、が
小手先の技術ではなく、本当に膨大なグローバルIPを割り当てる方法および実装は、ipv6以外に2015年時点では存在せず
djbが書いた大昔の予言どおりの現在であったとしても、v4との互換性がないのがアレだといわれても、2015年の今日、でデバイスレベルで実装があるのはipv6だけです。良い、悪いは置いといて、ipv6以外の代替案が無く、結果的にダメな点が多いと言われるipv6に変わるテクノロジーを作り、協議/策定することが15年たっても誰一人出来なかったというのが、今です。おかげでipv4で何とかする技術は発展したと言えますが。。

壮大な話になってしまいました。
歴史に関してはググればいくらでもでてくるので、興味があれば調べてください。また私個人はdjbの予言は的確であったと思うし、当時の私には大変分かりやすかった。djbと翻訳者ともに尊敬、感謝しています。
http://www.unixuser.org/~euske/doc/ipv6ex/

モチベーション

djbの言葉を借りますが、ipv4 -> ipv6 マイグレーションには相当な覚悟 という名のモチベーションが必要です。

2012年のipv6 dayを経て、
「やっぱりipv6はまだ人類には早すぎた」
と私は思いました。それ以前にも、大手のISPは、なんだかよくわからないサービスをipv6限定にして展開し、何とかipv6キラーコンテンツをつくろうとしていましたが、まあ、結果失敗してます(現状をみれば一目瞭然) しかし最近になって、少しずつモチベーションが高まりつつ有ります。1つは Docomo / AU におけるスマートフォンipv6 IPアドレスを本当にしゃべる時代が来たという点です。しかもその端末が何と私の手元にあるのです! Next5です!
http://trendy.nikkeibp.co.jp/article/pickup/20131209/1053986/?P=3

それともう一つ、最近、iOS9 からのアプリ審査には、ipv6対応にしておけ、ニュアンスとしては、ipv4縛りをするな!という方針が組まれるようです。
http://www.internetsociety.org/deploy360/blog/2015/06/apple-will-require-ipv6-support-for-all-ios-9-apps/

急にipv6対応とはご無体な話ですが、実際に「はい、明日からipv6でないアプリはNGね」って話ではなく、「インフラ側がipv6になっても動くようにしとけ」ということだと思います(そうじゃないと困る)。ちなみにiPhoneDocomo & AUはすでに ipv4 & ipv6両方いける、softbankipv6非対応のはず、2015/08/14現在。

AWSにおけるipv6

単刀直入にいうと、ipv6非対応です。
EC2-Classic & ELBでは可能だったらしいのですが、私はClassicを触ったことがないので分かりません。現在、新規アカウントでClassicは出来ないので、実質非対応です。また、この点については、Asure, GCP(GCE), も同様です。ぱっと思い当たるので対応しているのは softlayerです、あくまで2015/08/14時点の話。

ELBはipv4/6 dualstack?

EC2-Classicであればdualstack.<elbのエンドポイント> で、 AレコードもAAAAレコードも戻ってくるので、ipv6による接続が可能でした。しかし、EC2-VPC(今のEC2)では、どうやらこれは出来ないようです。

Internet-Facing Load Balancers - Elastic Load Balancing

実際に試してみました。EC2-VPCでELBを立てても、dualstak.ipv6. も作成されます、DNS上は。実際にipv6でアクセスした場合、Connection Refused となります。この原因は私の推測ですが
EC2-VPCでも ELB自体にipv6アドレスは付く、しかしSecurity Group や Network ACL で ipv6のアドレスは指定出来ない(::/0とか無理)。よって、通信出来ない。
SGは明示的に許可されていないアドレスからのアクセスは蹴りますが、Refuseではなく、無視するので、外しているかもしれません。

Security Group と Network ACL ipv6対応状況

現状のEC2は全てVPC内に構築するため、Network ACL および Security Group側で v6 IPアドレスを制御出来ないというのが現状です。よって、グローバルであれ、ローカルであれ、仮にEC2に直接 v6のIPアドレスがふれたとしても、別マシンと一切通信出来ないということです。
しかしIPプロトコル番号41 = ipv6 には対応しています。ということは、アレですな、4to6とかのトンネルです。

ipv6 tunnel brockerを使う

グローバルで参照可能なipv6アドレスを実現するには、ipv6 tunnel brockerのちからを借ります。Hurricane Elasticというところがやっています。

www.slideshare.net

HEで実装

Hurricane Electric Free IPv6 Tunnel Broker

アカウント作成などが必要なので、サクっと作ってっください。具体的な設定方法の大まかな部分は、ググれば出てくるので、特記事項のみ書きます。

トンネルの作成

IPv4 Endpoint(Your side) は、AWSの場合はEIPとなります。トンネルを作る前に、疎通確認をHE側から行うようなので、pingが通るようにSGをいじっておきます。 f:id:iga-ninja:20150815111205p:plain

f:id:iga-ninja:20150815111327p:plain
各種OS向けの設定方法も生成してくれます。何と親切!しかし、下記に注意
1. IPプロトコル番号 41 を通過できるようにしておく。これは HEのサーバIP(v4) と EIP(v4)の話
2. localに指定するIPアドレスは、AWSの場合、EC2(ENI)のローカルIPアドレス

動作確認はpin6とかで

$ ping6 ipv6.google.com
PING ipv6.google.com(nrt13s38-in-x0e.1e100.net) 56 data bytes
64 bytes from nrt13s38-in-x0e.1e100.net: icmp_seq=1 ttl=59 time=3.85 ms
64 bytes from nrt13s38-in-x0e.1e100.net: icmp_seq=2 ttl=59 time=3.89 ms
64 bytes from nrt13s38-in-x0e.1e100.net: icmp_seq=3 ttl=59 time=4.16 ms
64 bytes from nrt13s38-in-x0e.1e100.net: icmp_seq=4 ttl=59 time=4.01 ms

Nexus5からhttpを叩いてみる

一応DNSにAAAAレコードを追加して試したが、直打ちでも多分いける。Apacheアクセスログはこんな感じ

2001:240:2401:dumm:dumm:dumm:dumm:dumm - - [15/Aug/2015:01:15:06 +0000] "GET / HTTP/1.1" 200 27 "-" "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36"

uとかmとかないからダミー。もちろん本物のIPv6アドレスが取れます。ただ、端末から確認できるv6アドレスとは異なる。今のところNexus側には/64で割り当てられるらしいので、wifiルータとしてIPv6を他の機器に付けられたらとか思うけど、今のところそんな機能は無いらしい。

ipv6のフィルタリング

SGはipv4しか捌けないので、source IPとかで絞るなら、EC2上のip6tablesにお願いするしか無いので試してみた。
http://wiki.princo.org/?ipv6%A4%C7%A5%D5%A5%A3%A5%EB%A5%BF%A5%EA%A5%F3%A5%B0(netfilter%2Fip6tables) を参考にしました。

# ip6tables -A INPUT -p tcp -s <NexusのIP 上から64>::/64 --dport 22 -j ACCEPT
# ip6tables -A INPUT -p tcp --dport 22 -j DROP

上手く動いているようです。ただし、v4/v6両方でアクセス可能としていたら、v6だと思ってたらv4でアクセスしてたことがあったので注意。

まとめ

  1. 2015/08 時点で ipv6 をネイティブ対応出来ているのはsoftlayerのみ
  2. ELBはipv6IPアドレスを持つようだが、EC2-VPCでは疎通出来ない
  3. 外部からipv6アクセス可能にするには、ipv6 tunnel brokerでなんとかできる
  4. トンネルの中の通信をSGで制御出来ないので、ip6tablesなど、EC2内部で何とかする

自分が生きている間にipv6を触ることなんてないと思っていたけど、こんなに簡単に触れるなら、一発あるかも?モバイルやIoTなど、大量のグローバルIPアドレスはやっぱりあった方がいい、と言うのはわかっているけど、そこへ至る動機がなかった、けど、今はある。ただ、softlayer以外のクラウドコンピュティングがネイティブに対応するのはきっとまだまだ先。