お手軽だということで Redis をストレージエンジンとした Zipkin サーバーを使っていましたが、Redis ではメモリに収める必要があり、データ容量的に足りなくなったため Cassandra を使うことにしました。
ここでは CentOS 6.6 へ Cassandra と Zipkin サーバーをセットアップする手順を記しておきます。Zipkin をアプリに組み込む方法には触れません。
Zipkin とは Twitter が開発している分散トレーシングツールで、今ちょっとバズってるマイクロサービスを進めていくとどのサービスのどこが遅いのかを知る必要がでてきて、リクエスト毎にアプリをまたいだトレースができるツールです。「LINEのマイクロサービス環境における分散トレーシング « LINE Engineers' Blog」を読むとわかりやすいかもしれません。(お金があって対応言語などがマッチしていれば AppDynamics が良いと思いますけどね)
構成はサーバー4台それぞれに Zipkin と Cassandra をインストール。Zipkin Collector へのアクセスを HAProxy で4台に振り分ける。keepalived でうち1台に VIP をもたせる。(Ansible の Playbook 貼れば終わりなんだけど…)
Redis の時に Zipkin Collector が過負荷でどうしようもなかったので、Zipkin サーバーも複数並べたくて、どうせならということで Cassandra サーバーに相乗りさせてみましたが、Cassandra Cluster とは別にしても問題ありません。特に既に Cassandra Cluster が存在する場合など。また、Zipkin Query や Zipkin Web はブラウザからアクセスするサーバーなので沢山並べる必要もありません。今回はどれが故障しても慌てなくて済むように同じ構成のサーバーを並べています。
Cassandra のセットアップ
Cassandra のインストールは DataStax のパッケージを用います。このパッケージには /etc/security/limits.d/cassandra.conf
も含まれていて limit まわりも適切に設定してくれます。こういうの重要です。
[datastax] name = DataStax Repo for Apache Cassandra baseurl = http://rpm.datastax.com/community enabled = 0 gpgcheck = 0
$ sudo yum install --enablelrepo=datastax dsc20 cassandra20
vm.max_map_count を変更した方が良いようです。
$ echo 'vm.max_map_count = 131072' | sudo tee -a /etc/sysctl.conf $ sudo sysctl -p
Java の Heap Size は /etc/cassandra/default.conf/cassandra-env.sh
で設定します。
MAX_HEAP_SIZE
と HEAP_NEWSIZE
ですが、未設定の場合は搭載メモリから自動で計算されます。
/etc/cassandra/default.conf/cassandra.yaml
で Cluster Name と seed サーバーを設定します。4台のうち2台を seed としています。
cluster_name: 'Zipkin Cluster' seed_provider: - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: - seeds: "192.168.1.101,192.168.1.103" listen_address: 192.168.1.x rpc_address: 0.0.0.0
これで4台で sudo service cassandra start
すれば cluster が構成されます。
Zipkin 用スキーマの作成
https://github.com/twitter/zipkin/blob/master/zipkin-cassandra/src/schema/cassandra-schema.txt にスキーマの定義ファイルがあるのでダウンロードし、
$ cassandra-cli -h localhost -f cassandra-schema.txt
とすることで作成できます。
が、cassandra-cli
は deprecated とありますので cqlsh
で作ったほうが良いのかも。
cqlsh で DESCRIBE KEYSPACE "Zipkin";
と打てば定義が確認できます。
cassandra-schema.txt を流し込んだだけだと Replication Factor が 1 で4サーバーあっても1台停止すると使えなくなってしまうので 2 に変更します。ついでに class も SimpleStrategy にしてしまいます。複数 DC じゃないので SimpleStrategy
。
cqlsh> ALTER KEYSPACE "Zipkin" WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 2 };
ALTER KEYSPACE
したら nodetool repair
コマンドを実行する必要があります。
cassandra-cli
ではなく cqlsh
を使ってスキーマを作成する場合は DESCRIBE KEYSPACE
で表示される文を使えばできそうな感じです。
cqlsh> DESCRIBE KEYSPACE "Zipkin"; CREATE KEYSPACE "Zipkin" WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '2' }; USE "Zipkin"; CREATE TABLE "AnnotationsIndex" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "Dependencies" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "DurationIndex" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "ServiceNameIndex" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "ServiceNames" ( key blob, column1 blob, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'min_sstable_size': '52428800', 'class': 'SizeTieredCompactionStrategy'} AND compression={'chunk_length_kb': '64', 'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "ServiceSpanNameIndex" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='NONE' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'min_sstable_size': '52428800', 'class': 'SizeTieredCompactionStrategy'} AND compression={'chunk_length_kb': '64', 'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "SpanNames" ( key blob, column1 blob, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='NONE' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'min_sstable_size': '52428800', 'class': 'SizeTieredCompactionStrategy'} AND compression={'chunk_length_kb': '64', 'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "TopAnnotations" ( key blob, column1 bigint, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; CREATE TABLE "Traces" ( key blob, column1 blob, value blob, PRIMARY KEY ((key), column1) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance=0.010000 AND caching='KEYS_ONLY' AND comment='' AND dclocal_read_repair_chance=0.100000 AND gc_grace_seconds=864000 AND index_interval=128 AND read_repair_chance=0.000000 AND replicate_on_write='true' AND populate_io_cache_on_flush='false' AND default_time_to_live=0 AND speculative_retry='NONE' AND memtable_flush_period_in_ms=0 AND compaction={'class': 'SizeTieredCompactionStrategy'} AND compression={'sstable_compression': 'LZ4Compressor'}; cqlsh>
古いデータの掃除
放っておくとデータは溜まりっぱなしですが、Zipkin + Cassandra ではデフォルトで7日で TTL が設定されているので Cassandra の compaction を実行することで TTL を過ぎたデータを削除することができます。
nodetool compast
を定期的に実行しましょう。
OpsCenter
OpsCenter (http://www.datastax.com/what-we-offer/products-services/datastax-opscenter) を使うとかっちょいい画面でクラスターのモニタリングができます。
$ sudo yum install --enablerepo=datastax opscenter
今回はここのセットアップ方法は省略
Zipkin のセットアップ
/opt/zipkin
に collector, query, web をインストールします。
collector, query, web をそれぞれダウンロードします。
https://github.com/twitter/zipkin/releases/
- https://github.com/twitter/zipkin/archive/1.1.0.zip
- https://github.com/twitter/zipkin/releases/download/1.1.0/zipkin-collector-service.zip
- https://github.com/twitter/zipkin/releases/download/1.1.0/zipkin-query-service.zip
- https://github.com/twitter/zipkin/releases/download/1.1.0/zipkin-web.zip
zipkin-collector-seervice.zip, zipkin-query-service.zip, zipkin-web.zip はそのまま /opt/zipkin に展開します。
/opt/zipkin/zipkin-collector-service-1.1.0
/opt/zipkin/zipkin-query-service-1.1.0
/opt/zipkin/zipkin-web-1.1.0
1.1.0.zip は中の zipkin-web というディレクトリだけを取り出して /opt/zipkin/zipkin-web
に置きます。
Zipkin の各サービスは Supervisord を使って管理します。
EPEL リポジトリにあります
$ sudo yum install supervisor --enablerepo=epel
Zipkin 用ユーザーを作成します
$ sudo groupadd zipkin $ sudo useradd -g zipkin zipkin
/etc/supervisord.conf
を書いて sudo service supervisord start
で 8080/tcp で zipkin-web にアクセスできるはずです。 Zipkin へのデータ登録は 9410/tcp です。
[program:zipkin-collector] command = /usr/bin/java -Xmn1000m -Xms2000m -Xmx2000m -cp /opt/zipkin/zipkin-collector-service-1.1.0/libs -jar /opt/zipkin/zipkin-collector-service-1.1.0/zipkin-collector-service-1.1.0.jar -f /opt/zipkin/zipkin-collector-service-1.1.0/config/collector-cassandra.scala user = zipkin autostart = true stopwaitsecs = 10 log_stdout = true log_stderr = true logfile = /var/log/supervisor/zipkin-collector.log logfile_maxbytes = 10MB logfile_backups = 10 [program:zipkin-query] command = /usr/bin/java -cp /opt/zipkin/zipkin-query-service-1.1.0/libs -jar /opt/zipkin/zipkin-query-service-1.1.0/zipkin-query-service-1.1.0.jar -f /opt/zipkin/zipkin-query-service-1.1.0/config/query-cassandra.scala user = zipkin autostart = true stopwaitsecs = 10 log_stdout = true log_stderr = true logfile = /var/log/supervisor/zipkin-query.log logfile_maxbytes = 10MB logfile_backups = 10 [program:zipkin-web] command = /usr/bin/java -cp /opt/zipkin/zipkin-web-1.1.0/libs -jar /opt/zipkin/zipkin-web-1.1.0/zipkin-web-1.1.0.jar -f /opt/zipkin/zipkin-web-1.1.0/config/web-dev.scala -D local_docroot=/opt/zipkin/zipkin-web/src/main/resources user = zipkin autostart = true stopwaitsecs = 10 log_stdout = true log_stderr = true logfile = /var/log/supervisor/zipkin-web.log logfile_maxbytes = 10MB logfile_backups = 10 [supervisord] http_port = /tmp/supervisor.sock pidfile = /var/run/supervisord.pid minfds = 1024 minprocs = 200 nodaemon = false loglevel = info logfile = /var/log/supervisor/supervisord.log logfile_maxbytes = 10MB logfile_backups = 10 [supervisorctl] serverurl = unix:///tmp/supervisor.sock
接続先の Cassandra については /opt/zipkin/zipkin-collector-service-1.1.0/config/collector-cassandra.scala
, /opt/zipkin/zipkin-query-service-1.1.0/config/query-cassandra.scala
の中で指定します。デフォルトで localhost になっています。
もしもストレージに Redis を使いたい場合は config
ディレクトリにある collector-redis.scala
, query-redis.scala
を使います。 HBase 用のファイルもあります。
Redis ではアクセス(登録)が多いと collector プロセスが全然処理しきれなかったのであまり使われていないのかもしれません。Cassandra に変更したらサクサクになりました。
これで動作はするのですが、デフォルトのままでは collector が DEBUG ログを出力してログの量が多すぎるので次のように書き換えました。
私 Scala はまったくわからないので
https://groups.google.com/forum/#!topic/zipkin-user/NwZFPzYeo9I
を参考に同僚にやってもらったわけですが。
--- collector-cassandra.scala.orig 2013-08-27 14:55:06.000000000 +0900 +++ collector-cassandra.scala 2015-03-11 09:51:28.629353999 +0900 @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import com.twitter.zipkin.builder.{ZipkinServerBuilder, Scribe} import com.twitter.zipkin.builder.Scribe import com.twitter.zipkin.cassandra import com.twitter.zipkin.collector.builder.CollectorServiceBuilder import com.twitter.zipkin.storage.Store +import com.twitter.logging._ val keyspaceBuilder = cassandra.Keyspace.static(nodes = Set("localhost")) val cassandraBuilder = Store.Builder( @@ -25,5 +27,13 @@ cassandra.AggregatesBuilder(keyspaceBuilder) ) +val loggers = List( + LoggerFactory( + level = Some(Level.INFO), + handlers = List(ConsoleHandler()) + ) +) + CollectorServiceBuilder(Scribe.Interface(categories = Set("zipkin"))) .writeTo(cassandraBuilder) + .copy(serverBuilder = ZipkinServerBuilder(9410, 9990).loggers(loggers))
HAProxyで負荷分散
お好みでどうぞ
keepalived で VIP を持たせる
こちらもお好みで