読者です 読者をやめる 読者になる 読者になる

「AWS Summit Tokyo 2017」に登壇することになりました

こんにちはmatsです。 最近、更新をサボっていてそろそ書かないとと思い、久しぶりに更新してみています。

本ブログでも何回か書いていますが、IMではSpot fleetやECSといったAWSならではのサービスを数多く活用しております。

その点が評価されてなのか、この度「AWS Summit Tokyo 2017」にてお話する機会を頂きました!

弊社HP

corp.intimatemerger.com

AWS Summit Tokyo 2017 - イベントページ

www.awssummit.tokyo

基本的にはブログで書いているような内容+αのお話をする予定ですが、当日会場でしかお見せ出来ない内容もありますので、ぜひお越し頂ければと思います。 が、何の奇跡なのか皆さん暇なのか分かりませんが、既に満席となっておりますね。。。

セッション後はお話する時間がありますので、IMの技術やIMのAWSの使い方、マーケティングテクノロジーに興味のある方はお声がけ頂ければと思います。

それでは当日お会いしましょう。

docker基盤としてECSを採用しました②

こんにちはmatsです。

今回はdocker環境への全面移行について、運用環境や全体構成について書こうかと思います。

全体構成

f:id:intimatemerger:20170223123255p:plain

デプロイフロー

GitHubとCircleCIの連動を軸に自動化しています。流れ的には

  1. [GitHub] PRをmasterブランチにマージ
  2. [CircleCI] 自動テスト
  3. [CircleCI] docker build でイメージ作成
  4. [CircleCI] ECRに対してdocker push
  5. [CircleCI] API経由でECS Task Definitionを更新
  6. [CircleCI] API経由でECS Serviceを更新
  7. [ECS] Sevice設定を元にコンテナが展開される

masterブランチが勝手にデプロイされるので、デプロイという作業は行っていないような感じです。

また、ECSのコンテナの入れ替えはELBのヘルスチェックが通るまで古いコンテナを破棄しないため、Blue-Greenデプロイの様な感じになります。

Spot Fleet

dokcer基盤のインスタンスは全てSpot Fleetにて起動しています。 Spot Fleetの利用にあたってはcloud-initを駆使しているので、そのうちcloud-initに絞った記事を公開しようかと思います。

用途に応じてSpot FleetとECS Clusterを分けていますが、分け方としては

  • ジョブワーカー用の基盤はCPUのコア数指定でSpot Fleetを入札
  • Webサーバ用の基盤はインスタンスの台数指定でSpot Fleetを入札

のような感じです。

また、用途によらず基盤のオートスケールは行っていません。 理由としては

  • ECSのリソース予約がまだチューニング段階
  • オートスケール速度が緩慢
  • そもそもスポットインスタンスを使っているので、それほどコストメリットが効かない

などがあげられます。

ログハンドリング

詳細はまた別途書こうかと思いますが、基本的に

  • アプリケーションのログ → fluentdでBigQueryやMySQL、S3に収集
  • コンテナの標準出力 → logging-driverのsyslogでpapertrailへ

のような感じで行っています。papertrailでなくてもいいかともいますが、remote syslogに対応したサービスだとdocker engineから直接送ることが出来るので非常に楽に運用することが出来ます。

次回以降はもう少し個々の要素について細かく書いていこうかと思います。

docker基盤としてECSを採用しました①

こんにちはmatsです。

IMでは2016年の夏頃からdocker環境への全面移行を行っております。 一旦、一段落したので、色々とまとめてみようかと思います。

なぜECSを採用したのか

dockerの基盤としてはKubernetesやDocker Swarmなど、いくつか選択肢があり、IMでもいくつか検討してみましたが、下記の理由でECSを採用しました。

  • Spot Fleet との相性がいい
  • コンテナのオートスケールをCloudWatch連動で出来る
  • 管理画面をAWSがホストしてくれる
  • Docker Swarmをサポート予定(らしい)

色々理由はあるのですが、一番はサクッと試してみることができて、ラフに運用が出来るのが一番の理由だったりします。

何をdocker環境で動かしているのか

IMのほぼすべてのシステムがdocker環境で稼働しており、下記のようなサービスがあります。

  • Python製のアプリケーション(uwsgi)
  • Python製のジョブワーカー(SQS連動)
  • Elasticsearch

しかし、Aerospikeまわりで数ミリ秒レベルの高速性が要求される箇所に関しては、dockerのネットワークレイテンシの影響が強く出るため採用していません。

dockerの運用で気をつけていること

基盤のAMIはAWSが用意したものをそのまま使う

設定内容によっては、AWSが用意しているECS向けのAMIをそのまま使いにくいケースもあるかもしれませんが、IMでは自前でAMIをビルドすることは行っておりません。

下記のようなことを徹底しており、docker基盤のLinuxの入れ替えが簡単に行えるようにしております。

  • AWSが用意しているECS向けのAMIをそのまま使う
  • 設定変更はcloud-initで行う
無理にAlpine Linuxを採用しない

docker界隈ではそのイメージサイズの小ささからAlpine Linuxが注目されていますが、IMでは採用するケースはあまり多くありません。

IMのアプリケーションはpythonで書かれているものがほとんどですが、ライブラリの中にはC拡張のものも少なくなく、OSによってはインストールのハードルが高いことがあるので、ライブラリの動作確認が取れているDebian (Ubuntu)を採用するケースが多いです。

また、アプリケーションエンジニアの多くが自分で調べて解決できる環境という点でも、マイナーOSの採用は慎重になったほうがいいかと思います。

基盤の運用にはSpot Fleetを使う

運用負荷とサーバコストの観点から、Spot Fleetにて基盤の運用を行っております。

適切に運用できればオンデマンドの1/4〜1/5程度の費用で運用が出来ることと、オートスケールのように自動復旧が可能になることからECSの基盤としてはベストな選択かと思います。ただ、AMIの入れ替えについてはSpot Fleetの設定を作り直さなくてはいけなかったりするので、若干使いにくい部分もあります。

まとめ

全環境でSpot Fleetを利用できる様になったことでコストの圧縮につながり、また、アプリケーションをコンテナ化することで運用・開発効率が上がりました。

次回以降はそれぞれのサービスについて少し細かく書いていこうかと思います。

他社サイトに設置するjsタグを作る時のTips(prototype汚染の話)

こんにちは、g0eです。

アドテク業界ではjavascriptのタグ(以下、jsタグ)をクライアントサイトに埋め込んでもらうことがよくあります。 自社サイトの開発と違って、設置先のサイトでどのようなjavascriptが動いているかわからないので、他のjavascriptと競合しないように書く必要があります。

今回は他サイトに設置するjsタグを書く時に注意すべき事項の中の一つの、prototype汚染の話をしたいと思います。

プロトタイプ汚染の例

下記のコードを実行した後に、

// Objectのプロトタイプを汚染するコードの例
Object.prototype.hoge = function(){}

下記のような、オブジェクトpiyoをfor-inループで 調べるようなスクリプトを実行します。

var piyo = {foo: 1, bar: 2};
for(var k in piyo){
   console.log(k);
}

すると、コンソールに以下の文字列が表示されると思います。

foo
bar
hoge    /* !? */

そんなの当たり前だろ、と思った方はこの記事はここで読み終えていただいて大丈夫です。

あれ、hogeが混ざってきたぞ、という方はこのまま読み進めて下さい。

javascriptのプロトタイプチェーンの話はなかなか奥が深い話なので、今回は言及しませんが、上記のようにObject.prototypeやArray.prototypeなどに新しいプロパティを追加するコードがどこかにある場合、for-inループをそのまま使うのは非常に危険ということが伝わったでしょうか?

一昔前(むしろ大昔?)に流行ったprototype.jsというライブラリを利用しているサイトで、このようなプロトタイプ汚染に巻き込まれることがあります。

プロトタイプ汚染に強いfor文の書き方

hasOwnPropertyを使う

for-inループの中で取得したプロパティが、対象オブジェクト自身のプロパティ(つまり、プロトタイプチェーンをたどって取得したものではない)かどうかをチェックして利用するようにします。

var piyo = {foo: 1, bar: 2};
for(var k in piyo){
   if(piyo.hasOwnProperty(k)){
      console.log(k);
   }
}
Object.keysを使う

Object.keysを使ってプロパティの一覧を取得することでも、同じ効果を得ることができます。

var piyo = {foo: 1, bar: 2};
var keys = Object.keys(piyo);
for(var i=0;i<keys.length;i++){
   console.log(keys[i]);
}

まとめ

かなり駆け足になってしまいましたが、今回のまとめは以下の通りとなります。

  • ObjectやArrayのprototypeをいじらない(プロトタイプ汚染しない)
  • for-inループを使う時は注意する(使わない、もしくはhasOwnPropertyを併用する)

AWS Lambdaを使ってmackerelにEC2のSpotPriceを投げ込む

こんにちは。g0eです。

昨日、弊社のコーポレートサイトをリニューアルしました。 なかなかかっこよく仕上がったんじゃないかと満足している一方で、随分と更新をサボっていたこのブログにも、きちんとしたリンクが貼られてしまったので、とりあえず何か書いておこうと思います。

参考までにコーポレートサイト corp.intimatemerger.com

背景

弊社ではAWS上にスポットインスタンスをうまく活用して、elasticsearchのクラスタを構築しています。スポットインスタンスは価格をかなり抑えられる一方で、時々価格が高騰してインスタンスが強制ターミネートされてちょっとドキドキします。(昨年の11月下旬〜12月上旬は特に頻度が高かったような)

冗長化して片方のAZが全滅してもデータ欠損は発生しない構成にしたり、スポットインスタンスで十分な数が確保できない場合は、オンデマンドインスタンスが自動的に起動するようにしたり、いくつか対応策はとっているので実害はほぼなかったりするのですが(ここの話はまた機会があれば改めて書きたいなとは思っています)、まずはスポットインスタンスの価格をきちんとモニタリングして傾向をつかんで行こうというのが今回の投稿の背景だったりします。

目的

前置きが少し長くなってしまいましたが、本題としては、弊社で監視に使っているmackerelにEC2のスポットインスタンスの価格推移を投げ込んで、アラートをあげたり、中長期のトレンドを可視化していきたいという話になります。(mackerel→slackにアラートを投げてPC/スマホから監視していますが、チャートも一緒に表示されるのでなかなか便利です。)

実装

早速ですが、lambdaのコード貼っておきます。言語はpythonです。

コード書いたのが1ヶ月以上前なので若干記憶が曖昧なのですが、スポット価格を取得するAPIの仕様がちょっと特殊で、

  • 指定した期間で価格が動いた時のデータ
  • 価格変動がなければ最後に価格が動いた時のデータ

が返ってきていたと記憶しています。

コードではちょっとトリッキーですが、未来の時間を開始時間=終了時間で指定することで、APIをコールした時点で最新の価格だけを取得しています。(気持ち悪いですが、試行錯誤した中ではこれが一番良かった)

import boto3
import datetime, time, json, urllib2
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

MACKEREL_APIKEY = "XXXXXXXXXXXXXXXXXXXX" # mackerelのAPIキー
MACKEREL_SERVICE = "xxxxxxxxx" # mackerelのサービスを指定
EC2_INSTANCE_TYPES = ["m4.4xlarge","c3.2xlarge"] # 価格を調べたいインスタンスを指定
EC2_PRODUCT_DESCRIPTIONS = ["Linux/UNIX (Amazon VPC)"]

def post_mackerel (apikey, service, payload):
    headers = { 'Content-type': 'application/json', 'X-Api-Key': apikey }
    url = "https://mackerel.io/api/v0/services/{0}/tsdb".format(service)
    data = json.dumps(payload)
    req = urllib2.Request(url, data, headers)
    res = urllib2.urlopen(req)
    return res

def get_payload():
    cl = boto3.client("ec2",region_name="ap-northeast-1")
    t = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    res = cl.describe_spot_price_history( \
        StartTime=t,
        EndTime=t,
        ProductDescriptions=EC2_PRODUCT_DESCRIPTIONS,
        InstanceTypes=EC2_INSTANCE_TYPES,
    )
    payload = []
    now = int(time.time())
    for r in res["SpotPriceHistory"]:
        if r["AvailabilityZone"] == "ap-northeast-1a":
            az = "a"
        elif r["AvailabilityZone"] == "ap-northeast-1c":
            az = "c"
        else:
            raise Exception("Invalid AvailabilityZone")
        payload.append({ \
            "time": now,
            "name": "SpotPrice."+r["InstanceType"].replace(".","-")+"_"+az,
            "value": float(r["SpotPrice"]),
        })
    logger.info("payload length ... "+str(len(payload)))
    return payload

def lambda_handler (event, context):
    payload = get_payload()
    post_mackerel(MACKEREL_APIKEY, MACKEREL_SERVICE, payload)
    logger.info("done!!")

lambdaは、最近追加されたスケジュール実行の機能を使って、↓みたいな感じで5分毎に実行しています。 f:id:intimatemerger:20160116021742p:plain

結果mackerel上のサービスメトリックスに溜まったデータが↓みたいな感じになります。一回、スパイクしていますね…。 f:id:intimatemerger:20160116021901p:plain

今回の投稿は以上になります。ほぼ既存のコード貼っただけですね…。

はやく、LambdaがVPC対応しないかなぁ…。

Rundeckをクラスターモードで構築する

こんにちはmatsです。

最近、更新サボってましたが久しぶりの投稿です。

 

今回は前に下の記事でご紹介していたジョブスケジューラー「Rundeck」について書こうかと思います。

http://tech.im-dmp.net/archives/1681

最近、2.6.0がリリースされたのでアップデートを行ったのですが、改めてドキュメントを見なおしてみるとクラスター構成が組めるみたいだったのでチャレンジしてみました。

そんなに難しくはないのですが、ドキュメントがあまり綺麗にまとまっていなかったりするので、参考になれば幸いです。

 

Rundeckとは

rundeck-logotype-512

オープンソースで開発されているJava製のジョブスケジューラーです。

SSHで対象サーバに接続し、決められた処理を行えるのが基本機能になるのですが、EC2連携のプラグインを使うことでEC2側のタグやインスタンスタイプ、AZなどで対象サーバをフィルタリング出来のが非常に強力です。

rundeck1

IMではバッチ処理等の他にデプロイ処理や覚えにくいREST APIコールなども登録して運用しています。

 

Rundeckのクラスター構成

全体構成はこんな感じです。

rundeck_infra

設定やログを外出しできるので、構成的にはオートスケール出来るのですが、さすがにジョブの最中にライフサイクルが走ると困るのでやっていません。

EC2のインスタンスはそれほど強くなくて大丈夫なのですが、javaのout of  memoryが怖いのでt2.medium(RAM:4GB)を使っています。

また、クライアント証明書による認証を行う関係でnginxを挟んでいますが、ELB→Rundeck(jetty)の様に直接受けてもいいかもしれません。

 

設定まわり

 

基本的には yum や apt-get でインストールすればすぐに動くのですが、クラスターを組む際はいくつか設定を変更する必要があります。

 

設定をMySQLに持たせるようにする

デフォルトの設定だと下記のようになっていて、サーバ内に設定を保持してしまうのでMySQL内に持つように変更します。

/etc/rundeck/rundeck-config.properties

dataSource.url = jdbc:h2:file:/var/lib/rundeck/data/rundeckdb;MVCC=true;TRACE_LEVEL_FILE=4

これを下記のように書き換えます。

dataSource.url = jdbc:mysql://{{ mysql_host }}/{{ mysql_database }}?autoReconnect=true
dataSource.username = {{ mysql_user }}
dataSource.password = {{ mysql_password }}

# Enables DB for Project configuration storage
rundeck.projectsStorageType = db

# Encryption for project config storage
rundeck.config.storage.converter.1.type = jasypt-encryption
rundeck.config.storage.converter.1.path = projects
rundeck.config.storage.converter.1.config.password = mysecret

# Enable DB for Key Storage
rundeck.storage.provider.1.type = db
rundeck.storage.provider.1.path = keys

# Encryption for Key Storage
rundeck.storage.converter.1.type = jasypt-encryption
rundeck.storage.converter.1.path = keys
rundeck.storage.converter.1.config.password = mysecret

 

ログをS3に出力するようにする

下記設定を追加し、rundeck-s3-log-pluginを有効化します。

/etc/rundeck/rundeck-config.properties

rundeck.execution.logs.fileStoragePlugin = org.rundeck.amazon-s3

 

また、下記のようにログの出力先を設定します。

/etc/rundeck/framework.properties

#name of the bucket
framework.plugin.ExecutionFileStorage.org.rundeck.amazon-s3.bucket = {{ s3_bucket }}

#path to store the logs
framework.plugin.ExecutionFileStorage.org.rundeck.amazon-s3.path = logs/${job.project}/${job.id}/${job.execid}.log

#  AWS region name
framework.plugin.ExecutionFileStorage.org.rundeck.amazon-s3.region = ap-northeast-1

 

クラスターモードの有効化

下記設定を追加し、クラスターモードを有効化します。

/etc/rundeck/rundeck-config.properties

rundeck.clusterMode.enabled = true

 

また、個々のサーバを識別するためのserverUUIDを設定します。

値のフォーマットのバリデーションがあるみたいなので、uuidgen などで生成するのがいいかと思います。

/etc/rundeck/framework.properties

rundeck.server.uuid = 00000000-0000-0000-0000-000000000000

 

IMの場合は起動時に読まれるシェルスクリプト(/etc/rundeck/profile)内で生成するようにしています。

sed -i -e &quot;/^rundeck.server.uuid/c\rundeck.server.uuid = $(uuidgen)&quot; /etc/rundeck/framework.properties

 

ELBのスティッキーセッションを設定する

Rundeckはjettyというアプリケーションサーバが発行するCookieでセッション情報を管理しているので、ELBのセッション維持にこのCookieを用いるように設定します。

Cookie名は JSESSIONID です。

jsessionid

ちなみにこの設定をしないと別のサーバにバランシングされた瞬間にログアウトします。。。

 

 

あとはRundeckのプロセスを起動するだけでクラスターに参加します。楽ちんですね(´∀`)

 

 

おすすめプラグイン

大したものは入れてませんが、一応紹介しておきます。

rundeck-ec2-nodes-plugin

RundeckからEC2のAPIをコールして、インスタンス情報を取得することが出来るプラグインです。

 

rundeck-s3-log-plugin

ジョブの実行ログをS3におくプラグインです。

 

rundeck-slack-incoming-webhook-plugin

ジョブの通知をSlackのIncomming Webnoohに送るプラグインです。

↓こんな感じです。

Slack

 

 

Rundeckのクラスタリングについて日本語で書いてあるドキュメントはあまりないみたいなので、参考にして頂ければ幸いです。

(本家のドキュメントはこちら → http://rundeck.org/docs/administration/scaling-rundeck.html

 

 

AnsibleのPlaybookも公開しているので、Vagrantで試す際は使って頂ければと。

https://github.com/mats116/ansible-playbook-rundeck

Elasticsearch勉強会でLTしてきました

こんにちはmatsです。

 

昨日行われていた「第11回elasticsearch勉強会」でLTをしてきました。

今回は来日していたElasticsearchのCTO向けに英語スライドで事例紹介をとのオーダーがあったので、全編英語になっています。(さすがに日本語でしゃべりましたが)

内容的にはESというよりかはDMPとは?みたいな話が中心になりますが、世界的に見てもリアルタイムに検索クエリを投げることが出来るDMPはほぼ無い気がするのでいい事例なんじゃないかなと思います。

 

ちょっと失敗したのは、オーディエンスの特徴が出やすい(興味、関心が反映されやすい)デモ用のキーワードを用意していなかったことですね。。

参考までに「薄毛」で検索した時のキャプチャを載せておきます。

 

audiencesearch_demo

次があるのであればヘビーなAggregationに対応するためのチューニングの話とかをしたいですね。

 

おしまい。