Hatena::ブログ(Diary)

映画は中劇 このページをアンテナに追加 RSSフィード Twitter

2017-01-31

[] 時天空の間垣親方が亡くなった

時天空は飛び抜けた足技の達人でした。

ふつう足技は、他の技との連動として、あるいは相手の意表をかく技として使うものです。佐田の海は差して速攻の流れに乗って外掛けにいくのだし、舞の海の切り返しは左下手投げの反動を利用する技です。海鵬の内掛けは下手投げとのオプションでした。

時天空の足技はぜんぜん違う!時天空は、動きが止まった四つ身から、足技を警戒する相手が腰を引いて逃げるのもかまわず、容赦なく蹴返し、内掛け、ちょん掛け、二枚蹴りで刈り倒してしまうのです。ちょんちょんと蹴返しを飛ばして間合いをはかっている様子は、まるでナギナタ使いのようでした。

元時天空の間垣親方が亡くなりました。でも、彼の相撲が忘れられることはないでしょう。

2017-01-29

[] SparkのRDDとDataFrameでそれぞれwordcount

Sparkでデータ処理プログラムを書くためのAPIには、RDDとDataFrameの二種類がある。2つのAPIを用いてwordcountを書いてみる。wordcountは、テキスト中の単語の出現回数を数えるプログラムであり、分散データ処理の必修課題である。

RDDは低レベルなAPIで、データのレコードにはスキーマがない。データ処理は、map関数やflatMap関数などリスト処理的な高階関数によって記述する。reduceByKeyなどいくつかの操作は、レコードが(key, value)のタプルであることを要求するが、その検査はジョブ投入時ではなく、タスク実行時に行われる。総じて、古式ゆかしいMapReduceの感覚で扱える。

DataFrameは高レベルのAPIで、データのレコードにはスキーマが適用される。データ処理は、SQLによって記述するか、あるいはホスト言語上のDSL(以下クエリDSL)を用いてクエリを組み立てることにより行う。レコード内のデータに対する演算は、Rの式オブジェクトのようなものを組み立てて演算子に渡す。式オブジェクトの表現力が不足するときには、ホスト言語のクロージャを用いてUDFを定義し、クエリで用いる。総じて、RのDataFrameに、JPAのJPQLとCriteria APIを組み合わせたような感覚で扱える。

DataFrameの方が新しいAPIである。DataFrameの利点としては、ジョブ投入時点でスキーマの整合性が検査されること、スキーマを用いて実行計画の最適化が行われること、Hiveとの統合が容易なことが挙げられる。主流はこちらに移りつつあり、Spark上の機械学習ライブラリも、RDDベースのspark.mllibからDataFrameベースのspark.mlへの移行が予定されている。

環境構築

Google Cloud PlatformのCloud Dataprocを使う。Cloud DataprocはHadoop/Sparkクラスタを立ち上げるサービスである。

Dataproc APIを有効にした上で次のコマンドを実行し、「wc」という名前のHadoop/Sparkクラスタを立ち上げる。

gcloud dataproc clusters create wc --zone asia-northeast1-b

プログラムの共通仕様

  • ホスト言語としてPythonを用いる。
  • 次の3つのコマンドライン引数を取る。
    • 入力テキストのパス(ファイル / ディレクトリ)
    • 出力パス(ディレクトリ)
    • 頻出語とみなす出現回数
  • 入力テキストの全wordcountをCSV形式で出力パスに書く。
  • 頻出語のwordcountを標準出力に印字する。

RDDによるwordcount

#!/usr/bin/env python
# coding: utf-8
# vim: et sw=4 sts=4

import argparse

from pyspark import SparkContext

def wordcount_rdd(input_path, output_path, min_popular):
    # (1) RDDの操作はSparkContext起点に行う
    spark_context = SparkContext()

    # (2) テキスト行をレコードとするRDD
    lines = spark_context.textFile(input_path)

    # (3) 単語をレコードとするRDD
    words = lines.flatMap(lambda line: line.split())

    # (4) (単語, 1)をレコードとするRDD
    word_ones = words.map(lambda word: (word, 1))

    # (5) (単語, 出現回数)をレコードとするRDD
    word_counts = word_ones.reduceByKey(lambda count1, count2: count1 + count2)

    # (6) ここまでの計算結果を(8)以降で流用するためにキャッシュする
    word_counts.persist()

    # (7) CSV形式で出力
    csv = word_counts.map(lambda (word, count): u'%s, %s' % (word, count))
    csv.saveAsTextFile(output_path)

    # (8) 頻出語を絞り込む
    popular = word_counts.filter(lambda (word, count): count >= min_popular)

    # (9) ローカルにデータを取ってきて印字
    for (word, count) in popular.collect():
        print '%s => %s' % (word, count)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('input_path')
    parser.add_argument('output_path')
    parser.add_argument('min_popular', type = int)
    args = parser.parse_args()

    wordcount_rdd(args.input_path, args.output_path, args.min_popular)

単語ごとの出現回数を数えるには、同一単語のレコードを一ヶ所にかき集める必要がある。これを行うのが(5)のreduceByKeyである。reduceByKeyは入力に(key, value)のタプルを要求して、同一keyの全valueをreduceする。reduceByKeyに渡せるようにするため、(4)で(単語, 1)のレコードを作っている。1を出現回数分足し合わせれば、当然結果は出現回数になる。

プログラム中、flatMap, map, reduceByKey, filterはRDDから別のRDDを作るtransformation(変換処理)であり、saveAsTextFile, collectは一連のデータフローの結果を得るaction(アクション)である。Sparkのデータフローはアクションのメソッドが呼ばれるたびに起動される。

デフォルトでは、各アクションはデータフローの最初から計算をし直す。処理時間短縮のため、先行するアクションの途中結果を後続のアクションで流用するには、(6)のようにpersistするか、あるいはcacheする。cacheはオプション引数を付けてpersistを呼ぶだけのメソッド。

Google Cloud Storage (GCS) を入力元・出力先としてジョブを実行するには、SparkクラスタのいずれかのVMで、次のようにコマンドを実行する。

spark-submit \
  wordcount_rdd.py \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

GCSではなくHDFSを使うのであれば、パスは「hdfs://...」とする。

Cloud Dataprocでは、gcloudコマンドでジョブを投入することもできる。この場合、GCPのWebコンソールにてジョブの履歴が確認できる。

gcloud dataproc jobs submit pyspark \
  wordcount_rdd.py \
  --cluster=wc \
  -- \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

DataFrameのSQLによるwordcount

#!/usr/bin/env python
# coding: utf-8
# vim: et sw=4 sts=4

import argparse

from pyspark import SparkContext
from pyspark.sql import SparkSession, SQLContext
from pyspark.sql.types import StructField, StructType, StringType, ArrayType

def wordcount_sql(input_path, output_path, min_popular):
    # (1) DataFrameの操作はSparkSession起点で行う
    spark_context = SparkContext()
    spark_session = SparkSession(spark_context)

    # (2) UDFの登録等はSQLContext起点で行う
    sql_context = SQLContext(spark_context, spark_session)

    # (3) (value:StringType)をスキーマとするDataFrame
    text = spark_session.read.text(input_path)

    # (4) SQLで使えるようにビューとして登録
    text.createTempView('text')

    # (5) tokenize UDF
    sql_context.registerFunction('tokenize',
        lambda s: s.split(), returnType = ArrayType(StringType()))

    # (6) (word:StringType, count:LongType)をスキーマとするDataFrame
    word_counts = spark_session.sql(
            '''SELECT word, COUNT(*) AS count
            FROM (SELECT EXPLODE(tokenize(value)) AS word FROM text) words
            GROUP BY word''')

    # (7) 後続のアクションのためにキャッシュ
    word_counts.persist()

    # (8) CSV形式で出力
    word_counts.write.save(output_path)

    # (9) SQLで使えるようにビューとして登録
    word_counts.createTempView('word_counts')

    # (10) 頻出語を絞り込む
    popular = spark_session.sql(
        'SELECT word, count FROM word_counts WHERE count >= %d' % min_popular)

    # (11) ローカルにデータを取ってきて印字
    for record in popular.collect():
        print '%s => %s' % (record['word'], record['count'])

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('input_path')
    parser.add_argument('output_path')
    parser.add_argument('min_popular', type = int)
    args = parser.parse_args()

    wordcount_sql(args.input_path, args.output_path, args.min_popular)

DataFrameをSQLから参照するためには、(4), (9)のようにcreateTempViewでビューとして登録する。ビューの生存期間はSparkSessionと同じ。

SQLがデフォルトでサポートしない演算については、(5)のようにUDFを作る。これについては、組み込みのsplit関数で行けそうだけど、例示のためにUDFにしてみた。

(6)のSQL中で使っているEXPLODE関数は、レコード中の配列を縦持ちのレコード群に変換している。

ジョブを投入するコマンド。spark.sql.shuffle.partitionsは、GROUP BYが作るパーティションの数で、デフォルトは100。パーティションごとにタスクが実行される。自分が使ったデータでは、タスク起動のオーバーヘッドが大きかったので、2に変更している。DataFrameのrepartitionメソッドで、個別に指定もできる。

spark-submit \
  --conf spark.sql.shuffle.partitions=2 \
  wordcount_sql.py \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

gcloudコマンドを使う場合。

gcloud dataproc jobs submit pyspark \
  wordcount_sql.py \
  --cluster=wc \
  --properties=spark.sql.shuffle.partitions=2 \
  -- \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

DataFrameのクエリDSLによるwordcount

#!/usr/bin/env python
# coding: utf-8
# vim: et sw=4 sts=4

import argparse

from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.sql.types import StructField, StructType, StringType, ArrayType
from pyspark.sql.functions import udf, explode

def wordcount_df(input_path, output_path, min_popular):
    # (1) DataFrameの操作はSparkSession起点で行う
    spark_context = SparkContext()
    spark_session = SparkSession(spark_context)

    # (2) (value:StringType)をスキーマとするDataFrame
    text = spark_session.read.text(input_path)

    # (3) tokenize UDF
    tokenize_udf = udf(lambda s: s.split(), returnType = ArrayType(StringType()))

    # (4) (word:StringType)をスキーマとするDataFrame
    words = text.select(explode(tokenize_udf(text['value'])).alias('word'))

    # (5) (word:StringType, count:LongType)をスキーマとするDataFrame
    word_counts = words.groupBy('word').count()

    # (6) 後続のアクションのためにキャッシュ
    word_counts.persist()

    # (7) CSV形式で出力
    word_counts.write.csv(output_path)

    # (8) 頻出語を絞り込む
    popular = word_counts.filter(word_counts['count'] >= min_popular)

    # (9) ローカルにデータを取ってきて印字
    for rec in popular.collect():
        print '%s => %s' % (rec['word'], rec['count'])

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('input_path')
    parser.add_argument('output_path')
    parser.add_argument('min_popular', type = int)
    args = parser.parse_args()

    wordcount_df(args.input_path, args.output_path, args.min_popular)

(4)の「tokenize_udf(text['value'])」, (8)の「word_counts['count'] >= min_popular」は、この場で計算しているのではなく、ワーカノード上で実行するべき計算を、式オブジェクトとして作っている。(9)について言えば、「word_counts['count']」でcount列を表すColumnオブジェクトを取ってきて、Columnオブジェクトに定義された「>=」演算子で、計算結果の式オブジェクトに相当するColumnオブジェクトを生成している。

ジョブを投入するコマンド。

spark-submit \
  --conf spark.sql.shuffle.partitions=2 \
  wordcount_df.py \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

gcloudコマンドを使う場合。

gcloud dataproc jobs submit pyspark \
  wordcount_df.py \
  --cluster=wc \
  --properties=spark.sql.shuffle.partitions=2 \
  -- \
  gs://<バケット>/<入力テキスト> \
  gs://<バケット>/<出力> \
  100

所感

個人的には、ホスト言語の感覚でデータ処理が書けるRDDの方が好みである。

DataFrameを使うのであれば、できる限りSQLを使うのが良いと思う。クエリDSLを使うには、SQLの知識に加えて、クエリDSLと、式オブジェクトのDSLの知識も必要となる一方で、ホスト言語の型チェックによる型安全性が得られるわけでもない。利点が見つからない。

参考文献

2017-01-22

[] 大相撲2017年正月場所千秋楽

●5-2慶天海(押し倒し)高立6-1○

慶天海左を固めて潜ろうとするが、高立両手で突っ張って押し倒し。

●11-4宇良(右とったり)里山8-7○

里山一度つっかける。宇良右にいなすが里山向き直って、頭四つで組み手争い。里山引き落としから出るところ、宇良右引っ掛けて逆に押し込むも、里山左ハズに掛かってこらえてふたたび頭四つ。里山左肩透かしから地を這うような右とったり!

里山の技は華麗に決まるというのではなくして、忍の字の刻印が押されているというか、土の味がするというか、一種独特のおもむきがある。やめられない。

●7-8千代翔馬(寄り切り)佐田の海8-7○

佐田の海さっと左差して速攻きれいに寄り切り。幕尻の土俵際で残った。

○11-4高安(叩き込み)遠藤7-8●

高安右のぶちかまし。遠藤左おっつけて横を向かせて押して出るが、足が流れて高安の叩き込み。遠藤ついてない。

●8-7勢(叩き込み)正代7-8○

勢右かかえて小手投げ。正代向き直って左おっつけから出ると思いきや左に開いて突き落とし。決まり手叩き込み。

●9-6玉鷲(押し出し)豪風10-5○

低く当たった豪風が突っ張り合いで圧倒。なんだなんだ。

●4-11照ノ富士(寄り切り)琴奨菊5-10○

琴奨菊左前廻し引いてがぶる。上手が切れるが意に介さず、再度がぶって寄り切り。琴奨菊らしい良い相撲。

●11-4白鵬(左すくい投げ)稀勢の里14-1○

白鵬右張って左差し、右おっつけで体勢低くがぶって寄る。稀勢の里俵に掛かってなんとか持ちこたえて、左突き落とし気味のすくい投げで逆転、白鵬を土俵に這わせた。

今場所の稀勢の里らしい粘り腰の相撲。白鵬は、今場所の状態ではがっぷり組み合って悪いと思ったか、作戦成功ながらも稀勢の里が重かった。

2017-01-21

[] 稀勢の里の初優勝

全相撲ファンの息子こと大関稀勢の里が、ついに、やっと、初めての優勝を成し遂げました。

牛久の怪童萩原が幕内に出現して12年、待ちに待った大関昇進からさらに5年。優勝次点の勝ち星を12回も挙げながら、決定戦にすら縁がないその間に、ライバルたちは次々と優勝の本懐をとげ、日馬富士はもとよりカモにしていた鶴竜にまで綱取りの先を越され、大関の心中はいかばかりだったでしょうか。

待ちかねたわやい。

最近は、どっしり構えて相手充分に攻めさせて、おっつけから左を差して逆転する相撲で、前半戦を取りこぼさずに乗り切れるようになっています。遠からずチャンスは巡ってくると、信じているというか願っているというか、つまりは散々やきもきしていたわけです。

千秋楽結びの相手は、「勝って横綱になってみろ」との声を掛けられた白鵬です。ついに栄冠を手にした稀勢の里がどんな相撲を取るのか、楽しみです。

[] 大相撲2017年正月場所14日目

○6-1岩崎(引き落とし)阿炎5-2●

阿炎両手突きから突っ張り。岩崎これに頭を下げて応戦。阿炎が出てくるところを引き落とし。

○4-3明生(叩き込み)大成道3-4●

明生右、大成道左で踏み込む。押し合いから明生が右で首を叩いて叩き込み。

○6-8照強(引き落とし)栃丸3-4●

照強潜り込んで左を探るような立ち合いに、栃丸突っ張り。栃丸の右カイナを照強ハズ押しに押し込んで、パッと離れて引き落とし。

○7-7旭大星(右切り返し)大奄美9-5●

旭大星突き放しておいて右上手、グルグル振り回して二回転、機敏に右切り返し、左を突きつけて土俵中央に倒した。

今日は業師の旭大星。

○6-8旭日松(叩き込み)安美錦4-10●

旭日松思い切って左に変化。

○8-6小柳(押し出し)徳勝龍10-4●

小柳左ちょっと踏み込んでぶちかましから一気に押し出し。

●7-7里山(押し倒し)琴恵光8-6○

琴恵光の上突っ張り、里山左に左に回り込んでなんとか引っ掛けて絡め取ろうとするが、琴恵光ひるまず押しに押して押し倒し。里山あといちんち!

●3-11北太樹(押し出し)力真7-7○

力真両手突きから左右ののど輪で押し出し。

○10-4誉富士(押し出し)阿武咲8-6●

阿武咲左ハズから押して出るが、叩いたところに誉富士付け入って押し出し。

●4-10青狼(押し出し)天風9-5○

立ち合い三度合わない。天風の左がすぐに入って攻勢。青狼右に開いて逃げるが足が出た。

●5-9阿夢露(寄り切り)東龍5-9○

東龍すぐに右前廻しを引いて出し投げから寄り切り。

●4-10希善龍(突き出し)千代丸7-7○

千代丸両手から突っ張り。希善龍左に回り込むが、千代丸両のつき手がよく伸びて突き出し。

●8-6旭秀鵬(送り出し)剣翔8-6○

旭秀鵬が右を固めて当たるところ、剣翔左に大きく変わって後ろに付き、送り出し。

●6-8豊響(押し出し)竜電8-6○

竜電突っかける。豊響のぶちかましを竜電両おっつけ、豊響が引くところを押し出し。

○8-6山口(右下手出し投げ)英乃海6-8●

十両最後の一番。山口右かち上げだが、英乃海右四つに組み止める。山口左上手出し投げから頭を付け、右下手を取って下手出し投げ。

○11-3宇良(右すくい投げ)佐田の海7-7●

中入り後の取組。佐田の海が右差し、宇良右おっつけから差して左ハズ、右肩透かしからすくい投げ。佐田の海は宙返りしてしまった。

●6-8貴景勝(突き倒し)大栄翔11-3○

派手な突っ張り合い。大栄翔の引きに乗じて貴景勝が出たが、右を大きく踏み込んだところで大栄翔の右喉輪がカウンターで入って、もんどりうって倒れた。

●5-9臥牙丸(寄り切り)錦木5-9○

臥牙丸、錦木と突っかける。錦木左差し、右おっつけから上手前廻しで寄り切り。

●3-11大砂嵐(左突き落とし)石浦6-8○

石浦左に大きく変化。大砂嵐止まれず、そのまま出た。決まり手は、石浦の左がちょっと触れたのを見て突き落とし。

●8-6輝(左小手投げ)碧山8-6○

輝当たり勝って右差し、しかし間合いがあいたので碧山左から小手投げで逆転。

●7-7千代翔馬(押し出し)大翔丸6-8○

千代翔馬の突っ張りを大翔丸左に開いていなし、向き直ったところを押し出し。

○7-7千代皇(寄り切り)琴勇輝6-8●

琴勇輝出会い頭の両喉輪が強烈で押し込むが、千代皇左に回り込んで耐える。千代皇左おっつけから食いついて逆襲、左上手をつかんで寄り切り。

○9-5豪風(右上手投げ)千代鳳6-8●

千代鳳左を差して出るところ、豪風右上手投げで裏返しに。力が強いなあ。

●5-9千代大龍(左上手出し投げ)嘉風7-7○

千代大龍右喉輪から突っ張るが、引いて間合いがあいたところ、嘉風が逆に叩いて左上手を取り、突き落とし気味の上手出し投げ。

●3-11栃煌山(寄り切り)魁聖7-7○

栃煌山ガチッと当たって右差し、しかし魁聖が左上手を引きつけて寄り切り。栃煌山これで勝てないとなると厳しい。

○9-5千代の国(引き落とし)遠藤7-7●

千代の国左喉輪、右かっぱじくように当たって、間合いがあいたところを引き落とし。

○4-10隠岐の海(右突き落とし)妙義龍4-10●

妙義龍右おっつけ左ハズで出るが、隠岐の海右から突き落とし。妙義龍前に落ちやすくなったなあ。

●5-9宝富士(押し出し)松鳳山6-8○

松鳳山両喉輪づきから宝富士の左をひっぺがして突っ張りきった。

●8-6北勝富士(押し出し)御嶽海10-4○

未来の看板取組。御嶽海が終始突っ張り、北勝富士が左叩いたところを付け入って押し出し。

●10-4高安(右突き落とし)蒼国来11-3○

蒼国来左おっつけから左差し、両者左下手。高安が下手を切って寄って出て、決めにかかるところを蒼国来右から突き落としで体を入れ替えた。

○9-5玉鷲(素首落とし)荒鷲5-9●

荒鷲左前廻し掛かりかけるが玉鷲ひきはがす。荒鷲の上体が突っ込んだところを玉鷲右肩透かしからすくい投げ。決まり手は素首落とし。

○13-1稀勢の里(寄り切り)逸ノ城10-4●

稀勢の里両足飛びで踏み出し、左おっつけきって両差し。腰をおろして寄り切り。安心安心。

○8-6勢(寄り切り)琴奨菊4-10●

勢右差し、琴奨菊左かかえて寄っていくが、勢右すくい投げで体を入れ替えて寄り切り。

●4-10照ノ富士(寄り切り)正代6-8○

正代左上手出し投げから寄ったが詰めきれず、照ノ富士突っ張り返す。正代今度は両差しになって寄り切り。

●11-3白鵬(寄り切り)貴ノ岩11-3○

白鵬右を固めて左踏み込む。貴ノ岩右差して左おっつけから寄る!土俵際まで追い詰めて左上手を取り理想的な形に。白鵬俵に掛かって左小手投げを打つが、貴ノ岩体をあびせて寄り切り。

これにて稀勢の里優勝!

2017-01-10

[] Dockerのネットワーク構成

Dockerのネットワーク構成について整理する。

図1: Dockerネットワーク全体図

http://d.hatena.ne.jp/miyakawa_taku/files/2017-01-10_dockernw-01-whole.png?d=.png

物理NICが1個ついたDockerホストに2つのコンテナを立てると、図1のようになる。コンテナは172.17.X.Xのネットワーク内にいて、ホスト側には172.17.0.1のIPアドレスが付く。この構成自体は、VirtualBoxで言うところのホストオンリーネットワークと同じようなもの。異なる点として、Dockerネットワークは、ハードウェア仮想化ではなく、Linuxカーネルの機能であるvethペアとブリッジを組み合わせて実現される。

図2: vethペア

http://d.hatena.ne.jp/miyakawa_taku/files/2017-01-10_dockernw-02-veth.png?d=.png

veth (virtual Ethernet) は、図2のように、仮想NICのペアと、それをつなぐ仮想ケーブルを作る機能。ふたつの仮想NICはイーサネットで直接通信できる。

図3: ブリッジ

http://d.hatena.ne.jp/miyakawa_taku/files/2017-01-10_dockernw-03-bridge.png?d=.png

ブリッジとは、LinuxマシンがL2スイッチ(スイッチングハブ)として動作するように構成する機能。図3のように、ブリッジに紐付けられたNICは仮想L2スイッチのポートとなり、それぞれのNICにつながったホスト同士はイーサネットで通信できる。NICをブリッジに紐付けると、そのMACアドレスを使ったイーサネット通信はできなくなり、またIPアドレスを割り当てることもできなくなる。その代わり、ブリッジに対してMACが割り当てられるので、ブリッジを構成したホスト(図中のBridger Host)は、ブリッジのMACを使って、各NICの対向にいるホストとイーサネットで通信できる。

Bridger Host / Other Host 1 / Other Host 2は、イーサネットで相互に通信できるので、ひとつのネットワークセグメントが構成できる。

図4: Dockerネットワーク全体図(詳細)

http://d.hatena.ne.jp/miyakawa_taku/files/2017-01-10_dockernw-04-whole-detailed.png?d=.png

再度全体図に戻ると(図4)、Dockerホスト内にはひとつのブリッジが構成されて、そのMACにIPアドレス172.17.0.1が割り当てられる。また、コンテナごとに1つのvethペアが構成されて、片方の仮想NICはブリッジに紐付けられる。もう片方の仮想NICには172.17.X.XのIPアドレスが割り当てられて、コンテナに所属させられるもう片方の仮想NICはコンテナに所属させられて、172.17.X.XのIPアドレスが割り当てられる *1。vethペアは相互に通信でき、ホスト側の仮想NICはブリッジにつながっているので、ブリッジを通じてコンテナ/コンテナ間、コンテナ/ホスト間のイーサネット通信ができる。したがって、これらをまとめてひとつのネットワークセグメントとできる。

ホスト側でifconfigコマンドを普通に実行しても、コンテナに所属する仮想NICは見えない。また、ホスト側のプロセスがそのNICに紐付けられたIPアドレスを使って通信することも、普通にはできない。一方、コンテナ内のプロセスから見えるのは、コンテナに紐付けられた仮想NICだけで、たとえばホストの物理NICは見えない。これは、Linuxのネットワーク名前空間機能によって実現されている。コンテナごとにネットワーク名前空間がひとつ作られ、各コンテナ内のプロセスはこの名前空間上で実行される。仮想NICは、ホスト側のデフォルトの名前空間からコンテナの名前空間に移動されることによって、コンテナに所属する。ホストのシェルからコンテナに所属するNICを見るためには、netnsコマンドやnsenterコマンドで名前空間を切り替える。

ルーティング、NAPT、ポートフォワーディングは、Linuxカーネルのルーティング機能をそのまま使う。ここについては、Docker特有の論点も仮想化特有の論点もないと思う。

参考文献:

*1:2017-01-28に修正。IPアドレスの割り当てはコンテナのネットワーク名前空間で行われるため、元の記述は不正確でした。

2017-01-09

[] 朝青龍の円熟

朝青龍はとにかく速くて、強くて、ふつうなら無茶苦茶な技を仕掛けたって、危なくもなんともない。彼が進むあとから道理がゼーゼー言いながら追いかけて、追いついたころに本人はもう勝利の喜びに破顔一笑、大得意で花道を引き揚げている。

朝青龍が白鵬に本割の相撲で勝てなくなって、新しいスタイルを披露したのは2010年の正月場所だった。おっつけで相手の差し手を殺して、ハズ押しで相手の上手を遠ざけて、とにかく相手のいいところを全部消して、なにもできなくしてから、一撃で仕留める。見事な優勝で、朝青龍の円熟ってこういうことかと感心も得心もしていたら、例の暴行事件であっさり辞めてしまった。円熟した朝青龍が見られたのはそのひと場所っきりだった。

[] 止まるときは死ぬとき

かつての日馬富士も、真っすぐという点では貴ノ花や遠藤に引けを取らないけれど、彼は「生のイメージ」じゃないんだ。タガが外れてぶっ壊れるギリギリのスピードで真っすぐに突き破って、止まるときは死ぬとき、という感じ。

今の曲線的で技巧的な、老獪な相撲も好きです。日馬富士が円熟できるとは、思ってもいなかった。

[] ケレン味がないってこと

今日は正月場所の二日目で、北欧から来た二人連れと隣り合って話してたんだけど、遠藤がどうして人気なのか聞かれてすごく困った。ケレン味がない相撲、と答えたかったんだけど、「because he is handsome and ...」であとの言葉が見つからない。さいわい、今日の勢との一番が、まさにケレン味のない素敵な相撲で、すぐに分かってもらえた。

ケレン味がないってなんなんだろう。伊達公子さんのテニスについて、オザケンが同じことを別の言葉で書いてたはず。そう思って探したら「真っすぐな生のイメージ」だった。

そうなんだ、貴乃花が貴花田や貴ノ花だったころ、それから遠藤。ただ単に真っすぐな相撲ってんじゃなくて、ケレン味のない、「真っすぐな生のイメージ」。

たとえピッタリとする言葉が見つからなくたって、見ればすぐに分かるんだ。

2016-12-25

[] The man of the year 2016: 正代直也

今年の正月に新入幕、九州場所は西の3枚目で11勝を挙げたので、明けて来年の正月は新三役でしょう。

力の強いは百難隠すとはよく言ったもので、素人目にもあやふやな、狙いのはっきりしない相撲なのに、どんな体勢でもそれなりに取りこなします。

一番印象に残っているのは秋場所の貴ノ岩戦です。貴ノ岩が右四つで頭を付けて、おっつけから押し上げて前廻しを引いて、申し分のない運びで寄り切ろうとしたところ、さっきまでただ受けていた正代が、棒立ちからなんの脈絡もなく右の突き落としをズドンと一発でひっくりかえしてしまいました(決まり手はすくい投げ)。展開もなにもあったもんじゃない。

横綱大関と当たろうが初日から七連敗しようが、神経がないみたいに落ち着き払っているのも大物感たっぷりで魅力的です。

上位で安定して勝つためには技術の洗練も必要でしょうから、来年の正代の相撲がどんな変貌をとげるか、実に楽しみです。

[] The movie of the year 2016: 『放浪の画家ピロスマニ』

『放浪の画家ピロスマニ』 - Uplink

下高井戸シネマで見た、グルジアの画家ニコ・ピロスマニの伝記映画。グルジア語での上映は日本初だそうです。本当に美しい作品でした。

ピロスマニの絵は単純で、素朴で、平面的なのですが、この映画も、単純で、素朴で、平面的な映像で表現されています。とりわけて、乳製品のお店をひらいて、すぐにたたんでしまう一連の映像が素晴らしかった。大草原にポツンとお店が建っていて、とびらの左右には白黒の牛の絵が掛かっていて、入ると真正面に、奥行きも何もなく、ニコが棚の前で店番をしている。共同経営者の友人と仲違いしてお店をたたむ時は、とびらを出て友人がまっすぐ左に、ニコが絵を抱えてまっすぐ右に去っていって、それでおしまい。結婚式の舞踊の場面も美しかった。

絵のうまい下手について。ピロスマニの絵は、技巧に秀でているわけでは全然ない、と見えるのだけど、しかしあれを下手とは言えない。良い絵だと思う。ニコは純粋芸術として、あるいは芸術市場のために絵を描いていたわけでは全然なくて、トビリシの街の人たちの店や家の壁に掛けるために、具体的な注文に応じて具体的な絵を描いています。絵の描き方は誰に教わったわけでもない独自のものとして完成されていて、それについては専門家としての自負を持っていて、ときには注文主との喧嘩も辞さない。芸術市場の評価に翻弄されても彼のスタンスが変わることはなくて、死後100年経った今、遠い土地で彼の絵を見ていいなあ、と思っている。不思議ですね。

[] The book of the year 2016: カレル・チャペック『山椒魚戦争』

山椒魚戦争 (岩波文庫)

山椒魚戦争 (岩波文庫)

人間が奴隷としてこき使ってきた山椒魚が反乱を起こして世界終末戦争に突入する荒唐無稽なお話。『白鯨』みたいに(架空の?)百科事典を引いたり、新聞の切り抜きやらラジオ放送やら条約の文面やら、ガチャガチャで楽しい仕掛けです。

訳者解説は、同時代のファシズムを風刺する意図について云々していて、たしかにヒトラーらしき人物も登場するのだけど、全体としては明らかに植民地主義についての小説です。「山椒魚は文明の恩恵にあずかって幸せそうだし、我々は良いことをしているよね」みたいな、典型的なアポロジストも登場します。

[] The record of the year 2016: Saturday Looks Good to Me『All Your Summer Songs』

All Your Summer Songs

All Your Summer Songs

Heavenlyの最初のアルバムみたいな、キュートでハッピーでキャッチーでグルーヴィーなローファイギターポップ。

あとは、コクトー・ツインズの『Treasure』を初めて聴きました。素晴らしかった。