KubernetesにおけるCNI, flannelとcalico

CKADのテスト勉強をしている。事前に試験対象のクラスタの概要が提示されているのだけど、そのクラスタが4つあって、CNIが「flannel」と「calico」のどちらかだった。そもそもCNIというものを詳しく知らないし、flannelとcalicoの差は全くわからなかったのでメモ。

CNIとは

github.com

上記がCNIの仕様書

CNI=the Container Networking Interface

Kubernetes ネットワーキング | 技術文書 | 技術情報 | VA Linux Systems Japan株式会社 こちらによると、CNIプラグインとは、何らかの単一のバイナリの実行ファイルのことを指す

CNI_COMMAND 等の環境変数を指定した上で標準入力に json を与えると結果が標準出力に json 形式で返ってくるという仕様になっています。

CNI Pluginが満たすべき振る舞い

  • コンテナをネットワークに追加する
  • コンテナをネットワークから削除する
  • コンテナのネットワークが期待通りかをチェックする
  • バージョンを返す

これができればいいらしい。

techblog.yahoo.co.jp

こちらによると、flannelが広く使われている。CoreOSはflannelを使う calicoはBGPを使ってピュアなL3ネットワークを作る。

このスライドにも書いてあるが、実装自体もどんどん変わっていっていて、過去の記述が現在ではすでにあてはまらないケースがありそう。

  • flannelはネットワーク機能のみを提供
  • calicoはそれに加えてポリシー管理も提供
  • calicoのほうがパフォーマンスがいい?
  • 現時点では、flannelもcalicoもそこまで本質的に差異はないぽい? くらいの理解で大丈夫そう。

Kubecon + CloudNativeCon China 2018 参加報告

2018年11月13日〜15日の3日間、上海で行われたKubecon + CloudNativeCon China 2018に参加してきた。そのフィードバック会が社内であったので、その資料。

中国に行ったのは高校の修学旅行以来だから、20年ぶり。そもそも上海は初めて。上海の発展具合は想像以上だった。 高校生のときに行ったのは北京と鄭州だったが、現地の人には「上海は最近すごい発展しているけど、北京はあんまり昔と変わってないよ」と言われた。

日本にいると、中国のビジネス状況をしる機会があまりない。視点はどうしても米国西海岸が最先端という固定観念で考えてしまいがちだが、近年そうでもない状況になってきていると思う。

Dockerのタグでlatestを気軽に使ってはいけない理由

latestってアンチパターンだよっていうのは複数の筋から教わったのだけど、理由をきちんと自分でわかっていなかったのでメモ

medium.com

vsupalov.com

  • latestはタグがついていないときにつくデフォルトのタグにすぎない
  • latestはダイナミックに書き換わらないので、あとからpushしたリビジョンにlatestが勝手につくことはない(latest使うなら毎回付け続ける必要がある)
  • 毎回タグを指定するのが良い。ただそれがセマンティックバージョニングがいいのか、gitのコミットハッシュがいいのかなどはそれぞれの考え方がありそう。

Scrapyをちょっと触ってみて

まだまだわかっていないが現時点でわかった知見をメモ

  • scrapy shell [url]であらかじめアドホックCSSセレクタを調べて、それをそのままSpiderにすれば早い
  • リンクをたどっていく形式のSpiderは CrawlSpiderを使うとよい。一般的なユースケースがカバーできる。
  • 要素が可変なテーブルなどで、スクレイピング対象のDOMが特定しにくい場合、「○○という文字列の次にあるtd要素」のように「次の」「前の」指定をしたいことがある。そのようなケースはCSSセレクターでは処理できない。代わりにxpathを使う。

例:”住所”という文字列の次にあるtd要素を取得

address = response.xpath("//*[text()='住所']/following-sibling::td").extract_first()

scrapyでYahooニュースをクローリング

これも書籍の通り。復習のためにメモを残しておく。 これが一番シンプルかつ実用的なサンプルといえる。

Scrapyの作法に沿ってプロジェクトを作る。

 ᐅ scrapy startproject myproject
 ᐅ cd myproject
 ᐅ scrapy genspider news news.yahoo.co.jp
 ᐅ tree myproject 
myproject
├── __init__.py
├── __pycache__
│   ├── __init__.cpython-36.pyc
│   ├── items.cpython-36.pyc
│   └── settings.cpython-36.pyc
├── items.py
├── middlewares.py
├── pipelines.py
├── settings.py
└── spiders
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-36.pyc
    │   └── news.cpython-36.pyc
    └── news.py

3 directories, 12 files

編集するのは、settings.py(設定ファイル), items.py(データのスキーマを定義する), spiders/news.py(スパイダーのロジックを記述)の3つ。

settings.py

(中略)
DOWNLOAD_DELAY = 1 #リクエストごとに1秒空ける
(中略)

items.py

import scrapy


class Headline(scrapy.Item):
    """
    ニュースのヘッドラインを表すItem
    """
    title = scrapy.Field()
    body = scrapy.Field()

spider/news.py

import scrapy

from myproject.items import Headline


class NewsSpider(scrapy.Spider):
    name = 'news'
    allowed_domains = ['news.yahoo.co.jp']
    start_urls = ['http://news.yahoo.co.jp/']

    def parse(self, response):
        """
        トップページのトピックス一覧から個々のトピックスへのリンクを抜き出してたどる。
        """
        for url in response.css('ul.topics a::attr("href")').re(r'/pickup/\d+$'):
            yield scrapy.Request(response.urljoin(url), self.parse_topics)

    def parse_topics(self, response):
        """
        トピックスのページからタイトルと本文を抜き出す。
        """
        item = Headline()
        item['title'] = response.css('.newsTitle ::text').extract_first()  # タイトル
        item['body'] = response.css('.hbody').xpath('string()').extract_first()  # 本文
        yield item
  ᐅ scrapy crawl news -o items.jl                                                    
2018-11-05 23:43:28 [scrapy.utils.log] INFO: Scrapy 1.5.1 started (bot: myproject)

(中略)

2018-11-05 23:43:43 [scrapy.core.engine] INFO: Closing spider (finished)
2018-11-05 23:43:43 [scrapy.extensions.feedexport] INFO: Stored jl feed (8 items) in: items.jl
2018-11-05 23:43:43 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 3455,
 'downloader/request_count': 13,
 'downloader/request_method_count/GET': 13,
 'downloader/response_bytes': 167867,
 'downloader/response_count': 13,
 'downloader/response_status_count/200': 11,
 'downloader/response_status_count/301': 2,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 11, 5, 14, 43, 43, 741275),
 'item_scraped_count': 8,
 'log_count/DEBUG': 22,
 'log_count/INFO': 8,
 'memusage/max': 50143232,
 'memusage/startup': 50143232,
 'request_depth_max': 1,
 'response_received_count': 11,
 'scheduler/dequeued': 10,
 'scheduler/dequeued/memory': 10,
 'scheduler/enqueued': 10,
 'scheduler/enqueued/memory': 10,
 'start_time': datetime.datetime(2018, 11, 5, 14, 43, 28, 234828)}
2018-11-05 23:43:43 [scrapy.core.engine] INFO: Spider closed (finished)

リクエストごとに1秒置いて実行していくので少し時間がかかる。 最後にクローラーの統計がダンプされるので、そこで結果のサマリーがわかる。item_scraped_countが一番わかり易い。

結果は items.jlというファイルに書き込まれているが、そのままだと日本語がエスケープされているので、jqを使えば日本語として読める。

 ᐅ cat news.jl | jq .          
{
  "title": "NHK鈴木奈穂子アナ 21日ぶり「ニュース7」復帰「ご心配をお掛けしました」ネット安堵の声",
  "body": " 10月16日から体調不良で休養していたNHK「ニュース7」(月~日曜後7・00)平日キャスターの鈴木奈穂子アナウンサー(36)が5日、21日ぶりに番組に復帰した。冒頭、サブキャスターの高井正智アナウックス)"
}
(以下略)

Scrapyでシンプルなクローラーを書く

クローリングハック あらゆるWebサイトをクロールするための実践テクニック

クローリングハック あらゆるWebサイトをクロールするための実践テクニック

クローリング&スクレイピングフレームワークであるScrapyを試してみた。 Scrapingはクローリングとスクレイピングを両方やってくれる大きめのフレームワークで、スクレイピングに特化したbeautifulsoupとはその点が異なる。

ここでは上記の書籍に沿ってやってみた。

ᐅ python -V    
Python 3.6.5

ᐅ scrapy version
Scrapy 1.5.1

スクリプト 最もシンプルなスパイダーの実装。 書籍のままだと動かなかった(サイト構成が変わっている)ので少し変えている。

import scrapy


class BlogSpider(scrapy.Spider):
    name = 'blogspider'
    start_urls = ['https://blog.scrapinghub.com']

    def parse(self, response):
        """
        トップページからカテゴリページへのリンクを抜き出してたどる。
        """
        for url in response.css('ul li a::attr("href")').re('.*/tag/.*'):
            yield scrapy.Request(response.urljoin(url), self.parse_titles)

    def parse_titles(self, response):
        """
        カテゴリページからそのカテゴリの投稿のタイトルをすべて抜き出す。
        """
        for post_title in response.css('div.post-header > h2 > a::text').extract():
            yield {'title': post_title}

実行

ᐅ scrapy runspider myspider.py -o items.jl
2018-11-05 22:49:33 [scrapy.utils.log] INFO: Scrapy 1.5.1 started (bot: scrapybot)
2018-11-05 22:49:33 [scrapy.utils.log] INFO: Versions: lxml 4.2.5.0, libxml2 2.9.8, cssselect 1.0.3, parsel 1.5.1, w3lib 1.19.0, Twisted 18.9.0, Python 3.6.5 (default, Apr 25 2018, 14:23:58) - [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)], pyOpenSSL 18.0.0 (OpenSSL 1.1.0i  14 Aug 2018), cryptography 2.3.1, Platform Darwin-18.0.0-x86_64-i386-64bit

(中略)

2018-11-05 22:49:37 [scrapy.core.engine] INFO: Spider closed (finished)

結果であるitems.jlの中身を見る jlはjson lineの略。こういうデータ形式があるらしい。行単位で1レコードがjsonになっているデータ形式

ᐅ cat items.jl 
{"title": "<a href=\"https://blog.scrapinghub.com/2016/10/27/an-introduction-to-xpath-with-examples\">An Introduction to XPath: How to Get Started</a>"}
{"title": "<a href=\"https://blog.scrapinghub.com/2016/08/25/how-to-crawl-the-web-politely-with-scrapy\">How to Crawl the Web Politely with 

(以下略)

Redis5とstream

Redis version5(stable)が出た。

[RELEASE] Redis 5 is out! - Google グループ https://groups.google.com/forum/m/#!topic/redis-db/l0OXDAlwosU

リリースノートの中ではstreamという新しいデータ構造が面白そう。

https://redis.io/topics/streams-intro

streamはログを抽象化したようなappend onlyのデータ構造。

ログの扱いって、今でも一般的なアーキテクチャだとファイルにいったん書いて、それをfluentdで拾ってどこかに投げ直す、というのが一般的になっている(GKEも内部的にはそうなっているし)。 それをこのredis streamで置き換えていけないだろうか。