tail my trail

作るのも使うのも、結局は、人なのだ

Ansible入門&ハンズオン資料を公開しました

少し時間が経ってしまいましたが、 先日Ansible入門というイベント でAnsibleの説明とハンズオンをする機会を頂きました。 資料は SlideShare に公開しているので、これからAnsible始めたいという方はご参考にしていただければ幸いです。 資料の後半にハンズオン用の題材があり、Playbookの例はGithubに公開しています。

概要

Ansibleに興味がある未経験者〜使い始めた or 別の構成管理は使ったことがあるくらいのAnsible初心者をターゲットとし、受講後自走できるようにという思いで作りました 資料自体は、「構成管理とは」「なぜやるのか」という導入から始まり、Ansibleの世界観や登場人物をざっくりと説明しています。 読めばキーワードがざっと入って脳内インデックスができ、一時間半程度のハンズオンを経ればある程度勘所はつかめるようになるので、あとは公式ドキュメントなど見ながら自主学習してね、というスタンスの資料です。 ハンズオンは計3つです。

  1. 基礎編
    1. yumモジュールでパッケージインストール
    2. templateモジュールでバナーファイル作成
    3. yumモジュールでNginxインストール、serviceモジュールでNginx自動起動 (serviceモジュール実行時のdry-run時のエラーは無視する)
    4. copyモジュールで静的ページを配置
  2. adhoc編
    • ansibleコマンドでadhocにshellモジュールを実行する
  3. 応用編
    1. shellモジュールを活用してEC2 instanceにスワップ領域を追加する
      • register使って冪等性ちゃんと担保すること
      • スワップファイル作成は fallocateコマンドで
      • mkswap, swaponはもちろん、自動マウントも忘れずに
    2. yumモジュールでパッケージインストール
      • epelリポジトリ指定
      • rpmパッケージ指定 (これも冪等性ちゃんと担保するように注意)
    3. デプロイ
      • gitモジュールでデプロイ
      • Nginxの設定ファイルをtemplateモジュールで生成
      • デプロイないしは設定ファイルに変更があったらnotify使ってでNginxを再起動する

※ "冪等性ちゃんと担保する": 要するに、dry-run モード ansible-playbook --check で実行してchangedにならないようにする。

Playbookの基礎を抑えている内容となっていると思うので、大体の勘所がつかめ、すぐに業務自動化に励めると思います。 Ansibleが素晴らしいのは、エージェントレスでアーキテクチャも記法もシンプルなので、季節の環境を汚すことなくスモールスタートでねじ込める点です。 さくっと運用便利ツールや監視エージェントをばらまく、脆弱性対応するくらいのカジュアルな使い方から運転できるのは大きいですね。 私は去年まで1年ちょっとChefに使っていましたが、Ansibleのこのシンプルさはとても好きです。

SlideShare

ハンズオンの回答例

uorat/ansible-handson · GitHub

感想

当日は台風の影響もあり、申し込み数ほどに参加者は集まらなかったのですが、参加頂いた方は皆熱心に臨んでくださったので救われましたw "収穫が多かった、タメになった" という感想を幾つか頂きました。 小さいイベントですが、せっかく来て頂いた方には何かを持ち帰ってアクションに繋げられるようにしたいと思ったことと、この資料も今回に閉じず社内勉強会などで使いまわしたいという思いがあり、それなりに考えてコンテンツ作ったので、こういう声は純粋に嬉しいですし、今後の励みになります。 ご協力頂いた皆さま、ありがとうございました。

なお、ハンズオンのgit module使ってデプロイするというお題の中で、 @yteraoka さんの ansible-tutorial - Github を参照させていただきました。

以下のgithub.ioですね。ServerspecでCI回すところまで含まれていて素晴らしいTutorialです。 是非こちらも参考になさって下さい。

Ansible チュートリアル | Ansible Tutorial in Japanese

あと、この本もおすすめです。

入門Ansible

入門Ansible

naoyaさんのChef入門と同じくらいのボリューム感ですね。

入門Chef Solo - Infrastructure as Code

入門Chef Solo - Infrastructure as Code

Socket.io with Websocket の SSL/TLS 対応

※ (2016/9/19 追記) Nginx 使った対応方法も記載しているので、あわせて参考にして下さい。


昨今のサービスにおいて、暗号化はもはや必須の流れである。 GoogleFacebookなど主要なサービスはずいぶん前からHTTPS通信を標準としているし、HTTPS化対応しているサイトはSEO的にも優遇されるようになる という方針が出ていたりする。

前記事でSocket.IO + Redis PubSubを用いたリアルタイムメッセージ配信の仕組みをまとめたが、このままWebSocketを利用すると当然インターネット上を平文のテキストが流れてしまう。 また、チャット機能を呼び出す親元のWebページがHTTPSで提供されているものであれば、Mixed Content でブラウザによっては暗号化されていないWebSocket通信をブロックされることもあるだろう。 後からSSL/TLS対応を入れると往々にしてハマるので、ハナから対応しておくに越したことはない。

ということで、Socket.IOを用いたWebSocket通信をSSL/TLS対応させる。 以下、プログラム側での対応方法を記したが、NginxをWebSocket Proxyとして利用 できるので、NginxでSSL Terminationさせる手もある。

ポイント

f:id:uorat:20150830185442p:plain

  • ブラウザによってはWebSocket通信が利用できないことがあるため、Socket.IOでは、クライアントに適した通信プロトコルを自動選択する仕組みが実装されている。
  • セッションを張り続けて通信を行うWebSocketを使う以上、ボトルネックになりがちなロードバランサーを介することは推奨できない。
    • ロードバランサは最初の接続確立のみ利用し、WebSocket通信はクライアントからサーバに対して直刺しさせる

例えばAWSで運用している場合、SSL TerminationをELBに任せることが多いと思うが、WebSocket通信をELBを介さず行うため、サーバサイド (Socket.js on Node.js) で暗号化させる必要がある。

実装

サーバサイド

サーバに事前に配置したサーバ証明書、中間CA証明書、秘密鍵のセットをロードさせて起動することで、SSL対応が可能。 この対応で、Listenしたポートでhttps, wss通信が可能となる。

var fs = reqquire('fs');
var io = require('socket.io').listen(3000, {
    key : fs.readFileSync('/etc/pki/tls/private/your.domain.com.key').toString(),
    cert: fs.readFileSync('/etc/pki/tls/certs/your.domain.com.crt').toString(),
    ca: fs.readFileSync('/etc/pki/tls/certs/your.domain.com.cer').toString(),
    'log level':1
});

当然、証明書のドメインとWebSocket接続時にクライアントから指定するドメインは揃える必要がある。 ワイルドカードで証明書を作成していれば、例えば hostname.domain.com で接続させれば良い。

DNSにホスト名で名前解決できるようにA recordを登録しておく必要あり

クライアントサイド

以下のとおり、https で指定する。 "wss"ではない。 前に述べたとおり、handshake時にどのプロトコル (WebSocket / xhr-polling) で対話するかを採択するが、その通信は httpないしはhttpsで行われる。 handshakeが成功すると、wss または xhr-poolling over TLS で接続確立される。

<script src="https://hostname.domain.com:3000/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('https://hostname.domain.com:3000');

    ... your logic ...
</script>

ソース (※ 2016/5/15 追)

※ローカルのメモを転記しておく。

Socket.IOのdocumentaionにはSSL/TLS対応に関する記載が見当たらなかったが、ソース読んでみると明解。

Socket.IO

Socket.IO のコードを読んでみると、 `require('socket.io').listen した時に options.key があれば https#CreateServer 呼びだすロジックになっている。 ちょっと古いが、socket.io (v0.9.13) /lib/socket.io.js l63 - l66 抜粋

if (options && options.key)
  server = require('https').createServer(options);
else
  server = require('http').createServer();

Node.js - https.createServer

Official Documentationにあるとおり、 https.createServer の options は tls.createServer と同様で、以下のフィールドでSSL/TLS通信に必要なフィールドを指定できる。

https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener

終わりに

以上、Socket.IOでのWebSocket通信のSSL対応の一例としてプログラム側での対応方法を記したが、NginxをWebSocket Proxyとして利用 できるので、NginxでSSL Terminationさせるという方法もある。 手っ取り早く試すなら上記でも良いが、既にNginxを投入済みのサイトであればNginx WebSocket Proxyを用いても良いかも。

※ (2016/9/19 追記) Nginx 使った対応方法も記載しているので、あわせて参考にして下さい。

実践Node.js プログラミング (Programmer's SELECTION)

実践Node.js プログラミング (Programmer's SELECTION)

Socket.io + Redis PubSubでリアルタイムメッセージ配信

とあるサービスに「チャット機能」を追加しようという話になり、急ピッチで仕組みを用意することになった。 仕様/要件はふわっとしているものの、 2週間後にはリリースというケツは決まっている。 はてさてどうしたものかとその瞬間は思ったものだが、無事仕組みとして載せられたので、備忘記しておく。

リアルタイムチャット機能

要件は以下。

  • クライアントはWebブラウザとネイティブアプリ (iOS, Android)
  • 視聴者に軽量なテキストメッセージをbroadcastする
  • メッセージの永続化必須
  • 可用性/負荷分散も考慮する

例えば、動画を視聴しているとして、その動画の横に、自分含む視聴者のコメントが流れてくる、 ようなものを想像してもらえれば良い。

Socket.IO

「クライアント - サーバ間のリアルタイムなメッセージのやり取り」ということで、

という方針は早々に決まった。自動的にサーバは Node.js に決定。 ブラウザからのアクセスがあるため、xhr-polling / WebSocket をSocket.IO が両方サポートしていることは有難かったし、WebSocket通信を数行で始められる手軽さも魅力的だった。 ケツが2週間後なので実装コストがなるべく少なく済み、ビジネスロジックに集中できる方法を採択する必要があったため。

Socket.IOのポイントをまとめると、

  • WebSocket, xhr-polling をサポート、クライアントの動作環境に応じていずれかを自動採択する
  • "1 : 1" や "1 : N" のようなBasicな特定双方向通信、一斉配信 (broadcast)、接続単位を論理的に分離する 名前空間/namespace や 部屋/room 機能 など様々な配信方法をサポート

インストールは npm で一発 。 実装イメージは以下。

server side

var io = require('socket.io').listen(3000);
io.sockets.on('connection', function (socket) {
  
  ... your logic ...
  
});

client side

<script src="http://your.domain.com:3000/socket.io/socket.io.js"></script>
<script>
  var socket = io('http://your.domain.com:3000');
  socket.on('connect', function() {

    ... your logic ...

  });

  ... and so on...
</script>

ネイティブアプリ用のクライアントライブラリもある。 Socket.IOのGithub を見たところ、 C++ の実装もあるようだ。

Redis PubSub

Socket.IO on Node.js なWeb/Appサーバに対して、各クライアントはWebSocket通信で接続する。 接続数増と耐障害性を担保するためWeb/Appサーバの冗長化を考えた時に、一斉配信するメッセージを複数台のサーバにどのように伝達させるかで一瞬悩んだ。 所謂Publish / Subscribe ... ということで思い出したのがRedis。 "危険なほどのスピード" で有名なオンメモリKey-Value データストアで、扱えるデータ構造がMemcached と比べて豊富、ディスクへの永続化もあり人気のKVSだが、このRedis、PubSub も提供している。

1. 購読者 (subscriber) 、任意のkeyをsubscribeする

$ redis-cli
127.0.0.1:6379> SUBSCRIBE hoge
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hoge"
3) (integer) 1

2. (別のターミナルで) 出版者 (publisher) 、1で指定したkeyでメッセージをpublishする

$ redis-cli
127.0.0.1:6379> PUBLISH hoge "hello pubsub"
(integer) 1

3. 購読者 (subscriber) の端末に、2で配信されたメッセージが表示される

$ redis-cli
127.0.0.1:6379> SUBSCRIBE hoge
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hoge"
3) (integer) 1
1) "message"       ←追加
2) "hoge"          ←追加
3) "hello pubsub"  ←追加

Socket.IO - Redis の連携は容易。Socket.IOのバージョンによりインストール方法が若干変わる点に注意。

  • Socket.IO v0.9 系まで
    • Socket.IO 内に同梱されているため、特に追加インストールは必要なし。
    • 呼出は require('redis')
  • Socket.IO v1.0 以降
    • 外部ライブラリとして切り離された socket.io-redis をインストール
    • 呼出は require('socket.io-redis')

ちなみに、AWSのElasticCacheはRedisをサポートしており、Read Replica & Multi-AZも対応している。

PubSubはReplica Nodeに対しても有効で、PubSub sessionに関しても負荷対策が可能。

  1. Node.jsからReplica Nodeに対してSubscribe sessionを張っておく
  2. コメント投稿はMaster Nodeに対してPublish
  3. Read Replicaに対してSubscribeしている全購読者に配信される

WebSocketとELB

Socket.IO on Node.js サーバを冗長化させた場合の振り分け方も一工夫必要だった。 通常のHTTP通信と異なるため。注意点は以下の2点。

  • Cient - Server間の通信は通常のHTTP通信と異なりWebSocketで接続持続される
  • Socket.ioによるhandshake処理は必ず同一サーバに接続させなければならない

例えば、ロードバランサーを上段に挟む場合、全てのセッションがロードバランサーに対して張られることとなる。 通常のHTTPと異なりWebSocketのセッションを張り続けるという特性上、接続を集約させるロードバランサーボトルネックになる危険がある。 また、ロードバランサーによっては、Socket.IO/WebSocketの間に立てない場合がある。例えば、AWSのELB。

AWSのSolution Architectも、ELBは最初のセッション確立時にのみ利用してdispatchしてWebSocketさせるのが良いと言及している。

教えに従い、接続情報を返すAPIをクライアント側で呼出してから、サーバに対して直接WebSocket通信させる方式とした。

システム構成

行き着いたシステム構成は以下。

主機能がApache / PHPで動くWebアプリケーションのため、APIは既存資産を流用して実装。 Apahce/PHP を Node.js/Socket.IOと同梱させAPIを提供するようにした。 同梱させた理由は、接続情報管理の実装を省き自分のホスト名を返すようにしたかったため。 本来は、APIサーバは切り離して、ステータス含めて接続情報を管理するようにしたほうが望ましいと思う。

f:id:uorat:20150830185442p:plain

次回は、WebSocket接続のSSL/TLS対応をまとめようと思う。

Redis入門 インメモリKVSによる高速データ管理

Redis入門 インメモリKVSによる高速データ管理

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services パターン別構築・運用ガイド

Amazon Web Services パターン別構築・運用ガイド