くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

Kibanaにログイン機能を実装してみた

はじめに

みんなのマーケットでSREチームに所属しています、千代田です。
記事を書くのは2回目ですので、もし良ければ、
前回の記事(Prometheusを用いたSupervisor上のプロセス監視)も読んでみてください。

Kibanaとは

Kibana is an open source data visualization plugin for Elasticsearch.
It provides visualization capabilities on top of the content indexed on an Elasticsearch cluster.
Users can create bar, line and scatter plots, or pie charts and maps on top of large volumes of data. wikipedia

要約すると、
Elasticsearchのデータに対して可視化/分析などを行うことができるツールです。

Kibanaにログイン機能を実装できるプラグインの例

  • X-Pack
    公式から出されているプラグインです。
    機能としては、Security、Alerting、Monitoring、Reporting、Graph、Machine Learningなど幅広くカバーされています。
    こちらのライセンスモデルは、BASIC、GOLD、PLATINUM、ENTERPRISEがあります。
    ログイン機能が使えるのはGOLD以上のライセンスとなります。
    また、無償のトライアルでも30日ほど試すことができます。

  • Search Guard
    今回はこちらのプラグインを使用しています。
    機能としては、Securityに特化しています。
    こちらのライセンスモデルは、Community、Enterprise、Complianceがあります。
    今回実装したいのは、ログイン機能のみなのでCommunityを利用します。

今回の構成

最新バージョンを使用する場合(マイナーバージョンを含む)は、
適宜読み替えてください。

  • AWS: ec2(proxy server + elastic server)
  • Elasticsearch: 5.6.7
  • Logstash: 5.6.7
  • Kibana: 5.6.7
  • SearchGuard: 5.6.7-6

ELKスタック導入

導入済みを前提としているため、割愛します。

本記事における各アプリケーションとディレクトリは次のとおりです。

アプリケーション ディレクトリ
Elasticsearch /opt/elasticsearch
Logstash /opt/logstash
Kibana /opt/kibana

Search Guard導入

公式のドキュメントを読んだ上で以下を読み進めていくことを推奨します。

ElasticsearchにSearch Guardのプラグインをインストールします

cd /opt/elasticsearch
bin/elasticsearch-plugin install -b com.floragunn:search-guard-5:5.6.7-19

TLSのセットアップ

複数の作成方法がサポートされています。
詳しくは公式のTLS Setup > Generating Cerificatesの章を確認してください。
今回は公式で用意されているオフライン用のスクリプトを使用しますが、必要に応じて変更してください。

mkdir /tmp/search-guard-tlstool-1.1
cd /tmp/search-guard-tlstool-1.1 
wget https://search.maven.org/remotecontent?filepath=com/floragunn/search-guard-tlstool/1.1/search-guard-tlstool-1.1.tar.gz -O search-guard-tlstool-1.1.tar.gz
tar xvzf search-guard-tlstool-1.1.tar.gz
rm -rf search-guard-tlstool-1.1.tar.gz
cd ..
sudo mv search-guard-tlstool-1.1 /opt/elasticsearch/

TLSのコンフィグを作成します

cd /opt/elasticsearch/search-guard-tlstool-1.1/config
vim tlsconfig.yml

必要に応じて値を変更してください。

ca:
   root:
      dn: CN=root.ca.example.com,OU=CA,O=Example Com\, Inc.,DC=example,DC=com
      keysize: 2048
      validityDays: 3650
      pkPassword: auto
      file: root-ca.pem
defaults:
      validityDays: 3650
      pkPassword: auto
      generatedPasswordLength: 12

      # 今回は外部との暗号化をproxy serverで実装しているため、falseにしてあります。
      # Set this to true in order to generate config and certificates for
      # the HTTP interface of nodes
      httpsEnabled: false
nodes:
  - name: node1
    dn: CN=node1.example.com,OU=Ops,O=Example Com\, Inc.,DC=example,DC=com
    dns: node1.example.com
    ip: 10.0.2.1
clients:
  - name: admin
    dn: CN=admin.example.com,OU=Ops,O=Example Com\, Inc.,DC=example,DC=com
    admin: true

sgtlstool.shを実行します

tlsconfig.ymlをもとに各種PEM、KEYを生成します。

cd /opt/elasticsearch/search-guard-tlstool-1.1/
./tools/sgtlstool.sh -c ./config/tlsconfig.yml -ca -crt
cd out
cp ./* /opt/elasticsearch/config/

elasticsearch.ymlを設定します

node1_elasticsearch_config_snippet.ymlの内容を既存のelasticsearch.ymlに追記します。

cd /opt/elasticsearch/config/
chown elasticsearch:elasticsearch node1.pem node1.key root-ca.pem
vim elasticsearch.yml
## 省略
searchguard.ssl.transport.pemcert_filepath: node1.pem
searchguard.ssl.transport.pemkey_filepath: node1.key
searchguard.ssl.transport.pemkey_password: PASSWORD
searchguard.ssl.transport.pemtrustedcas_filepath: root-ca.pem
searchguard.ssl.transport.enforce_hostname_verification: false
searchguard.ssl.transport.resolve_hostname: false
searchguard.ssl.http.enabled: false
searchguard.nodes_dn:
- CN=node1.example.com,OU=Ops,O=Example Com\, Inc.,DC=example,DC=com
searchguard.authcz.admin_dn:
- CN=admin.example.com,OU=Ops,O=Example Com\, Inc.,DC=example,DC=com

Elasticsearchを起動します

service elasticsearch start

sgconfigの設定をします

cd /opt/elasticsearch/plugins/search-guard-5/sgconfig/

sg_config.yml

今回は特に必要がないので、以下を除いてほかはすべてコメントアウトします。

searchguard:
  dynamic:

sg_roles.yml

既存の設定によって変更箇所が異なります。
使用しているindex名を変更しているなどある場合は、必要に応じて適切なパーミッションを設定してください。

sg_internal_users.yml

今回はadmin、kibanaserver、logstashを使用するため、その三種類を残します。

admin:
  hash: HASH
logstash:
  hash: HASH
kibanaserver:
  hash: HASH

HASHの箇所に設定するパスワードハッシュの生成方法も載せておきます。

cd /opt/elasticsearch/plugins/search-guard-5/tools
chmod u+x hash.sh
./hash.sh 
[Password:] パスワード入力
パスワードハッシュが返ってきます

sg_action_groups.yml

今回は特にいじる必要がない為触れませんが、特別なパーミッションをグループにしたい場合は変更してください。

sg_roles_mapping.yml

今回はadmin、kibanaserver、logstashを使用するため、その三種類を残します。

sg_all_access:
  users:
    - admin

sg_logstash:
  users:
    - logstash

sg_kibana_server:
  users:
    - kibanaserver

以上で、sgconfig関連の設定が完了です。

sgadmin.shを実行します

Elasticsearchにsgconfigの設定を反映します。
admin.keyのパスワードは/opt/elasticsearch/search-guard-tlstool-1.1/client-certificates.readmeに書いてあります。

cd /opt/elasticsearch/plugins/search-guard-5/tools/
./sgadmin.sh -cd ../sgconfig/ -icl -nhnv -cacert /opt/elasticsearch/config/root-ca.pem -cert /opt/elasticsearch/config/admin.pem -key /opt/elasticsearch/config/admin.key -keypass admin.keyのパスワード -h elasticsearch.host

Elasticsearchの確認をします

curl --insecure -u admin:sg_internal_usersのadminパスワード 'https://elastic.host:9200/_searchguard/authinfo?pretty'

KibanaにSearch Guardのプラグインをインストールします

cd /tmp
wget https://github.com/floragunncom/search-guard-kibana-plugin/releases/download/v5.6.7-6/searchguard-kibana-5.6.7-6.zip
cd /opt/kibana
bin/kibana-plugin install file:///tmp/searchguard-kibana-5.6.7-6.zip

kibana.ymlを設定します

cd /opt/kibana/config/
vim kibana.yml
## 省略
elasticsearch.username: "kibanaserver"
elasticsearch.password: "sg_internal_users.ymlのHASH前のkibanaのパスワード"
elasticsearch.ssl.verificationMode: none

Kibanaを起動します

service kibana start

logstash.ymlを設定します

cd /opt/logstash/config/
vim logstash.yml
## 省略
output {
    elasticsearch {
        hosts => ["elastic.host"]
        user => logstash
        password => sg_internal_users.ymlのHASH前のlogstashのパスワード
    }
}

Logstashを起動します

service logstash start

Kibanaのログイン画面を確認します

adminユーザーでログインします。
f:id:curama-tech:20180316111911p:plain

ログイン後は、普段どおりに使うことができます。

Search Guardを無効にする

elasticsearch.ymlに追記した箇所をコメントアウトしたうえで、searchguard.disabled: trueを指定してください。
kibana.ymlに追記した箇所をコメントアウトしたうえで、searchguard.basicauth.enabled: falseを指定してください。
logstash.ymlに追記した箇所をコメントアウトしてください。

削除をする場合は、公式の手順にしたがってください。

感想

以上で、簡単にログイン機能を実装できました。
公式のドキュメントを読むと分かるのですが、
設定がかなり細かくできるのでユーザーごとに分けるのが良さそうです。
また、一度パーミッションを決めた後に変更をする場合も再度sgadmin.shを実行するだけでよいといった手軽さもあります。
ログイン機能を実装するか迷っている方がいらっしゃれば一度試してみることをお勧めします!

次回は、WebエンジニアによるAirflowについての記事です!
また、一緒に働いてみたいといった方もぜひお待ちしてます!

Amazon Connect使ってみた!

CTOの戸澤です。 今回は、昨年AWSから発表されたAmazon Connectを使ってIVRを構築してみます。

f:id:curama-tech:20180309105522p:plain

Amazon Connectとは

Amazon Connectは、AWS上でコールセンターのシステム(IVR,PBX)を構築できるサービスです。 クラウドなのでスケールしやすく、使った分だけの従量課金です。 AWSのLexやLambdaとの連携、S3に録音を保存できるなどAWSの他のサービスを活用したシステムの構築ができます。

参考: Amazon Connect(簡単に使えるクラウド型コンタクトセンター)|AWS

利用にはAWS Console上で問い合わせフローを構築する必要があります。構築はGUIでドラッグ&ドロップするだけです。 インバウンド、アウトバウンド双方の通話をブラウザ上で動作するソフトフォンを使ってできます。

Amazon Connectは、2018/03/07現在、EU, バージニア、オレゴン、シドニーリージョンの4リージョンに展開されています。 東京リージョンには来ていませんが、シドニーリージョンで日本の050番号が取得できるようですので、今回はシドニーリージョンを使って試してみます。

なぜ電話システム(IVR)が必要なのか

みんなのマーケットには、コンサルティング本部という部署があり、くらしのマーケットのカスタマーサポートと出店者へのコンサルティングを行っています。

基本的には、メールでやりとりをしますが、出店者へのコンサルティング、カスタマーサポートは電話で行うことも多くあります。お客様と出店者それぞれ担当するスタッフが異なるため、問い合わせフローを使って振り分ける必要があります。

構築

Amazon Connectのウィザードが充実しているので、基本的にそれにしたがって構築していけます。

今回は簡単なIVRを作ってみます。 コールフローはインバウンドとアウトバウンドの双方に対応でき、簡単に

  • インバウンド: カスタマーからの着信 -> 対応可能時間の判定 -> ルーティング -> キューイング -> 担当者が受話
  • アウトバウンド: 担当者が発信 -> カスタマーが受話

という流れになります。

1.電話番号の取得まで

ウィザードにしたがって、

f:id:curama-tech:20180309105537p:plain

  1. インスタンスの作成
  2. 管理者の設定
  3. テレフォニーの設定(インバウンド、アウトバウンドの通話を許可するか)
  4. データストレージの設定(録音とログを保存するS3 Bucketを指定)

を進めます。

次に電話番号を取得します。 今回は日本の050番号を取得します。 050番号の他に、フリーダイヤルの0800番号も取得できます。

f:id:curama-tech:20180309105551p:plain

電話番号の取得ができたら、さっそく通話のテストをしてみます。 携帯から今回取得した050番号に電話してみると、携帯とAmazon Connect(ブラウザ)の間で通話ができました。

f:id:curama-tech:20180309105602p:plain

2.問い合わせフローで使う各種設定

問い合わせフローを構築する前に、問い合わせフローで使うオペレーション時間(電話対応する時間帯と曜日)、エージェントが受話するまでに待機するキュー、プロンプト(音声による案内)の登録を先に行います。

オペレーション時間

今回は、BusinessHoursという名前で新規にオペレーション時間を作成します。 タイムゾーンはJapan, すべての曜日、開始AM9時、終了PM5時に設定します。

f:id:curama-tech:20180309105617p:plain

キュー

InboundQueueという名前で新規にキューを作成します。 オペレーション時間をさきほど作成した、BusinessHours, アウトバウンド発信者ID番号を取得した050番号に、ID名を適当に設定します。

f:id:curama-tech:20180309105626p:plain

プロンプト

今回は、コールフローから次のプロンプトを登録しました。

  1. 「お電話ありがとうございます。」
  2. 「IVRのテストです。お問合わせの場合は1を、終了する場合は9を押してください。」
  3. 「只今の時間は営業時間外です。平日の9時から17時のあいだにあらためてお掛け直しをお願いします。」
  4. 「エラーが発生しました。申し訳ありませんが、しばらく経ってからおかけ直しください。」
ルーティングプロファイルとユーザーへの割り当て

ルーティングプロファイルを作成します。 ルーティングプロファイルは、エージェントが対応するキューの設定です。 InboundRoutingProfileという名前で新規に作成します。 キューはさきほど作成したInboundQueueを設定します。

f:id:curama-tech:20180309105638p:plain

現在、このインスタンスでは唯一の管理者だけがユーザーとして登録されています。インバウンド通話はこのユーザーがエージェントとなって受けるため、管理者のルーティングプロファイルを今回作成したInboundRoutingProfileに変更し、InboundQueueの対応ができるように設定します。

f:id:curama-tech:20180309105647p:plain

ここまで、必要な設定は完了しました。 次に問い合わせフローを構築していきます。

3.問い合わせフローを作成

今回はテンプレートを使わずに、ゼロから構築してきます。

f:id:curama-tech:20180309105703p:plain

フロー図のとおりですが、キューの設定 -> オペレーション時間の確認 -> 番号入力で分岐 -> キューへ転送 -> エージェントが受話 という流れに、各エラーハンドリングが付属している状態です。 この問い合わせフローを保存します。

さきほど取得した050番号はデフォルトでは別の問い合わせフローと紐付いているので、今回作成した問い合わせフローに紐付きを変更します。

f:id:curama-tech:20180309105716p:plain

ここまでの問い合わせフローの「保存」だけでは、着信しても使える状態になっていません。 最後に、問い合わせフローの「保存して発行」をしてデプロイします。

ここまで、IVRの構築が完了しました。

電話してみる

実際に使えるか試してみます。 Amazon Connectの管理画面 右上にある電話マークをクリックして、Contact Control Panel(CCP)を開き、Availableの状態にして待機しておきます。

1.インバウンド

携帯からAmazon Connectの050番号へ発信してみます。

f:id:curama-tech:20180309105728p:plain

f:id:curama-tech:20180309105737p:plain

プロンプトが再生され、1番を押下し、CCPから受話ができました。

2.アウトバウンド

f:id:curama-tech:20180309105747p:plain

Amazon Connectから携帯にアウトバウンドの電話をしてみます。 CCPのダイヤルパッドを使って、発信できました。 ただ、携帯(ドコモ)では、着信時に非通知設定になり050番号は表示されませんでした。

今後に期待すること

1. オペレーション時間に祝日を設定できる

オペレーション時間の設定では、曜日と時間帯別に対応の可否を設定できます。現在、利用している電話システムでは、祝日は対応時間外に設定できますが、それと同等の機能ができるとたいへん便利です。 とりあえずは、手動で祝日前に曜日単位で対応外に設定することで対応できそうです。(祝日明けに元に戻すのを忘れないようにする必要があります。また、Amazon Connectは、Lambdaと連携できるのでLambdaで祝日判定をすることもできそうです。)

2. 固定電話の番号を取得できる

現在、シドニーリージョンの日本の電話番号では050番号と通話料無料の0800番号が使えるようです。 フリーダイヤルの0120は場合によっては、東京03〜のような固定電話の番号でないとつながらない場合があります。

3. 番号通知してアウトバウンド発信できる

Amazon Connectからアウトバウンドの発信をするとドコモでは、非通知になりました(2018/03/08現在)。Qiitaの記事によると、番号通知は対応中のようです。

参考: Amazon Connect で非通知電話ではなく、番号通知で電話をかける方法 - Qiita

まとめ

  • Amazon ConnectでIVRを簡単に構築できる
  • アウトバウンドが非通知の表示になるので注意

みんなのマーケットでは、一緒に働いてみたいといった方をお待ちしてます。

次回は、SREエンジニアの記事です。

ReDashを利用して社内のデータを誰でも可視化できる環境を用意する話

こんにちはエンジニアののりすけです。

突然ですが、社内の同僚や上司に「このデータ出して」など、せっかく集中してコードを書いているのに 手が止まってしまったなんて経験はないですか?

「あのデータはこのテーブルに入っているのでSQL実行してくれれば。。。」

と思ってもDB接続権限ないか、とかそもそもSQLってなに状態だとどうしようもない。 かといってよく使うデータならバッチで出しとくかと思って定期的にバッチでデータ取得を行なって、ファイルサーバなど共有 できるとこに置いておく。大体次に言われるのは

「あのデータちょっと足りないから追加でこの項目と、あの項目もだして」 「。。。はい」

そんな経験を持つあなたに朗報です。 これ解決できます!(ちょっとした環境用意 + みんながSQLを少しだけ覚えるだけ)

この記事を読んでできること

  • Dockerを使ってReDash環境の構築ができるようになる

経緯

みんなのマーケットでは会社のデータを自分が必要なときに、必要な分だけ取得/分析ができる環境を ReDashというソフトウェアをもとに構築しています。

もともとは非エンジニアの方がSQLの勉強をできる環境を用意する目的で準備したのですが、今では各部門で業務に利用するなど、 自分で好きなデータを使って効率的に働くことができています。

具体的には、売上など会社の情報を表示するダッシュボードとして利用したり、仮説検証に必要な情報を 取得するなど様々な業務で利用できるようにしています。

ReDashとは

オープンソースで開発が進められている様々なデータソースを可視化するツールです。みんなのマーケットではデータソースとして PostgreSQLやAWS Athenaを利用しデータの取得/可視化を行なっています。

特徴として、対応するデータソースの多さが挙げられます。今回事例として取り上げるPostgreSQLなど各種RDBMSだけでなく、 AWS Athena, Google BigQuery, Google Analytics などの外部サービスにも幅広く対応しています。(すごい!)

参考URL:

構築手順

実際にReDashの環境を用意してみましょう。 今回は公式に用意されているdocker-composeファイルを利用してlocalに構築します。

Dockerについてはここでは説明を割愛しますが、すでに用意されてあるcomposeファイルを使ったりするので、 導入はかなり容易です。これを機にDockerを覚えても良いかと思います。(ってか覚えた方がすごく良いです、便利です)

1.ソースを取得します

$ git clone https://github.com/getredash/redash.git
$ cd redash

2.docker-composeファイルの中身を確認する

取得したソースにcomposeファイルの雛形が用意されています。 この記事を書いた時点ではdocker-compose.yml, docker-compose.production.ymlの2ファイルが 用意されています。

ファイル内のコメントにも記述されていますが、docker-compose.ymlは開発向けなので、ここでは docker-compose.production.ymlこちらのファイルを元に設定を記述していきます。

composeファイルをみるとわかるのですが、ReDash自体は複数のコンテナでサービスが構成されています。 それぞれ以下の役割を担っています。

  • server

    ReDashのWeb console画面を提供しています。基本的にこの画面を利用して ユーザはQueryの発行やデータの可視化を行います。ソースをみるとわかりますが、 Pythonで実装されおり、Application Serverとしてgunicornが使われています。

  • worker

    ReDashではデータの結果取得に時間がかかるようなQueryを扱うケースが多いため、実際のQueryを実行する処理 (実際にデータを取りに行ったりする処理)はこのWorkerコンテナが行なっています。 こちらもPythonで実装されceleryというPythonのjob queue処理を行うためのフレームワークが使われています。

    workerが処理を行う内容には主に、ユーザがアドホックに実行したQueryを処理するadhoc workersとスケジュールされた通りに Queryを実行するscheduled queries workersがあります。 この切り替えはcelery worker起動時に-Qオプションで制御され、1つのworkerで両方の機能を担うことも、 別々のworkerに分けることもできます。

  • redis

    celeryではQueueを貯めておく場所としてredisを利用することができます。このredis内にserverからjobがqueueに登録され、 workerが引き出して実行を行います

  • postgres

    PostgreSQl serverです。これは共有データを入れる方のDBではなく、ReDashのユーザ情報や作成したQuery/Dashboardの情報が格納されます。

  • nginx

    ReDash serverのリバースプロキシとして稼働するnginxです。

3.composeファイルを編集する

ファイルの冒頭コメントにも書かれているように推奨設定として以下が書かれています。今回はlocalで動かすため いくつかの設定を省いていますが、本番で設定と書いたものについては運用する際に必ず設定するようにしてください

  • cookie secretの設定 (本番で設定)

    server -> environment -> REDASH_COOKIE_SECRET にランダムな値を設定します

  • PostgreSQLのpasswordを設定 (本番で設定)

    postgres -> environment -> POSTGRES_PASSWORD で初回起動時にpasswordを作ることができますが、 composeファイル内にハードコードするのではなく.envファイルを使うなりして値を外部から注入できる形にする 方が良いと思います。

    .envファイル等docker関係の説明については割愛させていただきます。

  • Postgres containerの永続化

postgres:
  image: postgres:9.5.6-alpine
  # volumes:
  #   - /opt/postgres-data:/var/lib/postgresql/data
  restart: always

以下のように修正します

postgres:
    image: postgres:9.5.6-alpine
    volumes:
      - /opt/postgres-data:/var/lib/postgresql/data
    restart: always
  • Workerをadhocとscheduledで分ける (サーバの環境や利用者数に応じて設定を検討する)
worker:
    image: redash/redash:latest
    command: scheduler
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 2
    restart: always

これを以下のように修正

adhoc-worker:
  image: redash/redash:latest
  command: scheduler
  environment:
    PYTHONUNBUFFERED: 0
    REDASH_LOG_LEVEL: "INFO"
    REDASH_REDIS_URL: "redis://redis:6379/0"
    REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
    QUEUES: "queries,celery"
    WORKERS_COUNT: 2  # 運用する環境によって変更
  restart: always
scheduled-worker:
  image: redash/redash:latest
  command: scheduler
   environment:
    PYTHONUNBUFFERED: 0
    REDASH_LOG_LEVEL: "INFO"
    REDASH_REDIS_URL: "redis://redis:6379/0"
    REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
    QUEUES: "scheduled_queries,celery"
    WORKERS_COUNT: 2  # 運用する環境によって変更
  restart: always

特に、containerの永続化は忘れずに行うようにしてください。行わない場合、containerを破棄した際などにユーザ情報や作成したQueryの情報を失ってしまうため、こちらの設定は忘れないようにしてください。

また、localの環境にDBなどサンプルで利用するReDashのデータソースがない場合はcomposeファイルに合わせて記述しておくと、 別でインストール等手間は省けます

4.起動する

まず、ReDashのデータベースを作る必要があるため以下のコマンドを実行します

$ docker-compose -f docker-compose.production.yml run --rm server create_db

コマンドが終了したら各コンテナを起動します

$ docker-compose -f docker-compose.production.yml up -d

起動後、 localhost:80 にアクセスして以下の画面が出てくれば無事環境構築は完了です。

f:id:curama-tech:20180302120101p:plain

初回アクセス時にAdminユーザの情報を登録後以下のメイン画面が表示されます。

f:id:curama-tech:20180302120211p:plain

正しく構築が出来ているか確認するため以下の手順を実行します。

  1. 画面右上のメニューから管理ページへアクセスし、利用するデータソースを設定する

  2. QueryメニューのNew Queryを選択し、設定したデータソースが選択できることを確認する

  3. Queryを記述しExcecuteボタンを押してQueryの実行結果が表示されることを確認する

以上の3点が確認できれば正しく設定されていることがわかります。

使ってみる

ReDashにはデータを取得、可視化、共有をそれぞれ、Query, Visualizations, Dashboardという機能で 使うことが出来ます。

各機能の操作方法は以下のヘルプ参照してください。英語表記ですが操作手順については わかりやすく画像と、アニメーションでまとめられています。

https://help.redash.io/article/32-getting-started (英語)

また、細かい操作方法を知りたい場合は公式のヘルプが便利です

https://help.redash.io/ (英語)

使える人を増やそう

以上でReDashの構築と一通りの操作は終わりです。読んでいただいたように、簡単に構築ができるため、 ぜひ社内に構築して利用してください。

運用の注意点として、当たり前の話ですが、社内環境を構築する際には個人情報等の秘匿すべき情報は ReDashのデータソースとして扱わないようにする等の対応を忘れないようにしましょう。

また、みんなのマーケットで運用した経験から社内運用する上での重要な点をお伝えしておきます

SQLを読み書きできる人を増やす

SQLを使ってデータを取得するという特性上、環境を用意しただけでは、記事冒頭に書いた状況はどうしても発生してしまいます。 まずは数名規模 & SELECT文のみでも良いので勉強会などを通して使えるような状態でReDashを利用し始めると スムーズに運用を開始できると思います。

以上、社内のデータを誰でも可視化できる環境を用意する話でした。 また、一緒に働いてみたいといった方もぜひお待ちしてます! 次回は第一回に続きCTOが書く予定です。

TypeScriptをプロダクトで使って便利だった話

はじめに

みんなのマーケットでwebエンジニアとして働いている高橋です。

今回は弊社で採用しているTypeScriptについて簡単に紹介します。みんなのマーケットではNode.jsおよびAngular等のフロントのjsに対して、TypeScriptを全面的に用いています。本記事では実際にプロダクトでTypeScriptを使用してメリットと感じた点をいくつか挙げていきます。

TypeScriptとは

TypeScriptはJavaScriptにコンパイルすることができる、静的型付けプログラミング言語です。変換後のjsには、型情報は残りません。TypeScriptを用いることで、動的型付け言語であるJavaScriptに対して、実行前に静的型チェックを行うことができます。また、型定義機能以外にも、classやmodule機能の拡張なども行われています(こちらの日本語資料が詳しいです)。これだけでも十分便利なのですが、おまけ機能として、ES2015等からES5への変換も行うことができます。

メリット1: 変数・引数・戻り値等の型を明示できる

以下、簡単なコードを例にして、TypeScriptのメリットを紹介していきます。手元にTypeScriptの実行環境が無い方は、Microsoftが公開している↓のプレイグラウンドで、TypeScriptのコンパイルを試してみることができます。(Runを押すと結果を確認することができます。)

https://www.typescriptlang.org/play/index.html#src=function%20stringToNumber(s%3A%20string)%3A%20number%20%7B%0D%0A%20%20%20%20return%20parseInt(s%2C%2010)%3B%0D%0A%7D%0D%0A%0D%0Alet%20a%20%3D%20stringToNumber(1)%3B%0D%0Aalert(a)%3B%0D%0A

f:id:curama-tech:20180222115335p:plain

function stringToNumber(s: string): number {
    return parseInt(s, 10);
}

let a = stringToNumber(1); // 数値を渡しているためここでエラーが出る!
alert(a);

JavaScriptとTypeScriptの大きな違いとして、変数や関数に型定義を記述できる点があります。上記のコードでの: string: numberという記述がそれにあたります。TypeScriptのコンパイル時に、コンパイラは型チェックを行い、型の不整合があった場合にはエラーを発生させます。

例えば上記のコードにおいて、stringToNumber関数は引数に"100"のような文字列が来ることを想定しています。しかし、プレイグラウンド上でstringToNumber(1)の部分に赤線が引かれてある部分を見ていただければわかるように、ここでは型エラーが発生しています。なぜならば、この関数の引数として許容されるのはstring型であり、number型ではないからです。このように、動作前に型チェックを行うことによって、実行時に想定しない型が用いられてしまう可能性を軽減できます。

なお注意として、例えばstringToNumber("a")とした場合は、"a"はstring型のため、コンパイルは問題なく成功します。しかし実行時、関数からは NaNが返ってきてしまいます。NaNはnumber型ではありますが(https://github.com/Microsoft/TypeScript/blob/7b9ceb85fa4e19ade740faa2af2e00e62e16f7c9/lib/lib.es5.d.ts#L25 )、関数の戻り値に対してNaNを期待しない場合にバグを生む可能性があります。このような例については(少なくとも上記のコードでは)TypeScriptの機能で静的にチェックすることができません。ですので、TypeScriptを使用したからといって100%実行時エラーがなくなるわけでは無い点については注意が必要かと思います。

メリット2: 型がドキュメントの補助になる

メリット1から派生する恩恵として、例えばJavaScriptにて、

function hogeToFoo(hoge) {
  
  // ...なにか煩雑な処理...

  return fuga;
}

という関数があった時、「この関数には何を引数として渡せば良いのだろう?」と疑問に思うこともあるかと思います。そんな時、JSDoc等を用いてコメントベースで引数の情報をコードに残しても良いですが、例えばTypeScriptなら、

https://www.typescriptlang.org/play/index.html#src=interface%20Hoge%20%7B%0D%0A%20%20%20%20a%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20Fuga%20extends%20Hoge%20%7B%0D%0A%20%20%20%20b%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Afunction%20hogeToFoo(hoge%3A%20Hoge)%3A%20Fuga%20%7B%0D%0A%0D%0A%20%20%20%20%2F%2F%20...%E3%81%AA%E3%81%AB%E3%81%8B%E7%85%A9%E9%9B%91%E3%81%AA%E5%87%A6%E7%90%86...%0D%0A%20%20%20%20return%20%7B%20a%3A%20hoge.a%2C%20b%3A%20%22fuga%22%20%7D%3B%0D%0A%7D%0D%0A

interface Hoge {
    a: number;
}

interface Fuga extends Hoge {
    b: string;
}

function hogeToFoo(hoge: Hoge): Fuga {

    // ...なにか煩雑な処理...
    return { a: hoge.a, b: "fuga" };
}

として型情報を引数、戻り値に記述するだけで、この関数hogeToFooが、Hogeを引数に取りFugaを返す関数であることが一目瞭然となります。さらに、関数に適用する値についてはコンパイル時に静的チェックされるので、より実行時の安全が担保されます。

メリット3: 機能拡張・リファクタリングが容易

https://www.typescriptlang.org/ では、"JavaScript that scales."と標榜されています。この言葉が実感できる要素の一つとして、先述した静的型チェックの恩恵から生まれる、TypeScriptの機能拡張・リファクタリングの容易さがあるかと思います。例えば上記のコードにおいて、Hogeを拡張したいとします。すると、

interface Hoge {
    a: number;
    c: boolean; // プロパティを追加
}

interface Fuga extends Hoge {
    b: string;
}

function hogeToFoo(hoge: Hoge): Fuga {
    return { a: hoge.a, b: "fuga" };  // 型エラー!
}

Hogeに影響のある全ての部分において、このエラーがコンパイル時に発生します。つまり、Hogeの型付けを完璧に行っておけば、Hogeの拡張に対する影響範囲を漏れなく知ることができるということです。これで、開発時の心理的障壁を抑えることができます。

メリット4: ES2015以降のシンタックスが使える

TypeScriptにおいては、コンパイル時の設定でtarget: es5を指定することで、ES2015, ES2016, ... -> ES5の変換を行うことができます。例えば、オブジェクトのアサイン等を行うことができるスプレッドシンタックスを用いた場合は、

https://www.typescriptlang.org/play/index.html#src=const%20hoge%20%3D%20%7B%20a%3A%201%2C%20b%3A%202%20%7D%3B%0D%0Aconst%20fuga%20%3D%20%7B%0D%0A%20%20%20%20c%3A%203%2C%0D%0A%20%20%20%20...hoge%0D%0A%7D%3B%0D%0A%0D%0Aalert(fuga)%3B%0D%0A

const hoge = { a: 1, b: 2 };
const fuga = {
    c: 3,
    ...hoge
};

alert(fuga);

というコードが、

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var hoge = { a: 1, b: 2 };
var fuga = __assign({ c: 3 }, hoge);
alert(fuga);

と変換されます。Babelを用いれば実現できる機能ではありますが、TypeScriptにビルトインされているため手軽に使える点がメリットかと思います。

さいごに

以上、実際にプロダクション環境でTypeScriptを使用してメリットと感じた点をいくつか挙げてみました。 TypeScriptを使ってコードを書いてみたいという方は、ぜひお声掛けください。 次回はnrskさんの予定です。

Prometheusを用いたSupervisor上のプロセス監視

はじめに

みんなのマーケットでSREチームに所属しています、千代田です。

今回は、弊社で導入している、Prometheusを用いたSupervisor上のプロセス監視について紹介します。
また、最後の方にSREチームとしての今後の課題について書きました。

Supervisorとは

Supervisor: A Process Control System
Supervisor is a client/server system that allows its users to monitor and
control a number of processes on UNIX-like operating systems.

くらしのマーケットでは複数のPythonアプリケーションをWSGIアプリケーションとして動かしており、
アプリケーションを実行するためのアプリケーションサーバとしてuWSGIを利用しています。
さらにブランチ別での環境構築システムや弊社独自のブルーグリーンデプロイを実現するために、
uWSGIプロセスの管理としてSupervisorを使用しています。

今回の監視を導入するに至った経緯

過去にSupervisor上のプロセスがfailしたことに気がつかず、翌日に発覚するといったことがありました。
その為、Supervisor上のプロセスの状態について検知するしくみが必要だと判断し、今回の監視を導入しました。

構成

監視はPrometheusnode_exporterで行っています。
アラート通知はAlertmanager経由でSlackにPOSTしています。

f:id:curama-tech:20180215132027p:plain

導入

Supervisorの動いているサーバを用意します(導入済みを前提としている為、割愛します)。

監視対象のサーバに最新のnode_exporterを導入します

もしDockerをすでに運用されている場合には、
サーバに直接的なオペレーションを行うよりも、コンテナのnode_exporterを使用することを推奨します。

wget https://github.com/prometheus/node_exporter/releases/download/v0.15.2/node_exporter-0.15.2.linux-amd64.tar.gz
tar xvzf node_exporter-0.15.2.linux-amd64.tar.gz
cd node_exporter-0.15.2.linux-amd64
mv node_exporter /usr/local/bin/node_exporter

Supervisordのコンフィグを設定します

inet_http_serverを設定する必要がある為、コンフィグに追記します。

[inet_http_server]
port = 127.0.0.1:30002

設定後はSupervisordを再起動する必要があります。

node_exporterを起動します

node_exporterはデフォルトでポート9100を使用する為、必要に応じてポートを変更します。

sudo /usr/local/bin/node_exporter --web.listen-address=:30001 --collector.supervisord --collector.supervisord.url=http://localhost:30002/RPC2

以上で、監視対象のサーバの設定は終わりです。

Prometheusで確認してみます

監視側のサーバにPrometheusを導入/設定する部分については割愛します。

Prometheusのコンソールにて、Expressionにnode_supervisord_upを入力して実行します。

f:id:curama-tech:20180215132031p:plain

Valueについてですが、1が起動している状態で、0が起動していない状態になります。
また、より詳細な判定を行いたい場合は、node_supervisord_stateを使用してください。
こちらのValueについては公式の説明を見た方がわかりやすい為、リンクのみとさせていただきます。

Prometheusのコンフィグを設定します

今回はAWS上で実行している為、ec2_sd_configsを使用しています。 必要に応じて変更してください。

global:
  scrape_interval:     15s 
  evaluation_interval: 15s 
rule_files:
  - '/etc/prometheus/alert.rules'
scrape_configs:
  - job_name: 'node'
    ec2_sd_configs:
      - region: ap-northeast-1
        port: 30001
      - source_labels: [__meta_ec2_tag_Name]
        target_label: name

alerting:
  alertmanagers:
  - scheme: http
    static_configs:
    - targets: ['AlermanagerのIP:9093']

Prometheusにアラートを追加します

下の例は、example_processがFORで指定した時間の間、停止していた場合にアラートを上げます。
また、ANNOTATIONSの中でアラート時に送信する内容を設定しています。
今回の場合は、$labels.nameでホスト名を、$valueでアラートが上がった際の値を取得しています。

ALERT ExampleProcessRunning
  IF node_supervisord_up{job='node',group="example_process",exported_name="example_process"} == 0
  FOR 5m
  LABELS { serverity = "warning" }
  ANNOTATIONS {
    firing_text = "Name: {{ $labels.name }}\n Value: {{ $value }}"
  }

AlertManagerで通知を行うようにします

今回は、Slackに通知を行います。
api_urlにはWebhookのendpointを設定してください。

global:

route:
  receiver: 'slack'
  group_by: ['name', 'alertname', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 24h


receivers:
- name: 'slack'
  slack_configs:
    - api_url: 'https://hooks.slack.com/services/END_POINT'
      channel: '#channel'
      title: '[PROBLEM] {{ .GroupLabels.alertname }}'
      text: '{{ .CommonAnnotations.firing_text }}'

実際にアラートを送信します

supervisorctlでアプリケーションを停止させ、5分ほど待つとアラートが飛んできます。

supervisorctl stop example_process

f:id:curama-tech:20180215132034p:plain

以上で簡易的にですが、アラートを送ることができました!
必要に応じてsend_resolvedなどのパラメータを用いれば、改善したことを通知するアラート等を流すこともできます!

SREチームとしての課題

現在、くらしのマーケットでは各サーバで動いているアプリケーションをDockerへ移行していく為の準備を進めています。
その為、AWS ECS/EKS(サービス前ですが)などを用いての運用/監視の体制を作っていかなければいけません。
しかしながらDockerに関しての知見がまだまだ浅い為、こういった新しいものへの挑戦/解決方法が今後の課題となりそうです。

最後までお読みいただき、ありがとうございました!
また、一緒に働いてみたいといった方もぜひお待ちしてます!

次回は、Webエンジニアによる、TypeScriptの紹介記事です。