読者です 読者をやめる 読者になる 読者になる

Symantecの証明書問題私的まとめ

Symantecの証明書でアレコレざわついているので、自分用にまとめます。

経緯等

以下にまとまっていまるので省略。

notchained.hatenablog.com

何がおこるか?

https://github.com/sleevi/explainer/blob/master/README.md

ざっくりまとめると以下の2点

  • 該当の証明書の有効期限を短いモノとして取り扱う(最終的には最大279日とみなす)
  • 該当の証明書はDomain Validatedとして扱う(EV証明書がEV証明書として扱われなくなる)

「該当の証明書」って?

https://chromium.googlesource.com/chromium/src/+/master/net/data/ssl/symantec/README.md

  • 証明書チェーンのルートCAの証明書が ここになければセーフ
  • subCAで発行した証明書の場合、それが"Excluded Sub-CAs"に記載されていればセーフ
  • それ以外はアウト

もっと具体的には?

個人的観測範囲で使われていて、該当するCAは以下

3/27くらいからChromeでEV証明書がEV証明書として扱われなくなったんだけど?

WindowsChrome 57では現象を確認できたが、MacChrome 57では確認できず。

経緯は以下

斜め読みしたカンジでは、"Symantecに激おこ事案"とは無関係の単なるバグか?
# タイミング悪すぎやろ。。。

どうすればいいの?

今後どう転ぶかわからないし、巻き込まれたくないならSymantecVeriSignのEV証明書をつかってるトコは、他社に乗り換えればいいと思う。

EV証明書じゃないなら、有効期限が短くなるだけといえば、それだけなので運用回避もアリかと。

私見

Symantecはギルティでも、Symantec発行の証明書を使ってるトコまで巻き込まれるのはツライ。

ただひたすらツライ。

docker swarmのオーバーレイネットワークの安定性について

半年くらい前にこんな記事を書いたのですが、まあうまく行きませんでした。

okzk.hatenablog.com

頂いたブコメも試してみたんですけど、結果は芳しくなく。。。

Re: Dockerに載せたサービスをホットデプロイする - okzkメモ

--stop-grace-periodの設定とDockerfileのHEARTBEATとSTOPSIGNALの設定をすれば出来るはず

2016/08/17 06:19
b.hatena.ne.jp

そんな中、元記事のヒトも試してみたようですけど、同じ結果に。。。

h3poteto.hatenablog.com


そんな中、CVE-2016-9962も出ちゃったし、docker 1.13もリリースされたコトだし、ということでdocker 1.13でswarmモードをもう一回試してみました。

インストール後、swarm初期化

# docker swarm init
# docker service create --update-delay 5s -p 80:80 --name test --replicas 2 nginx

別ウインドウでログ等を確認しつつ

# watch -n 0.3 'curl -sI  http://localhost; docker ps -q | xargs -i sh -c "echo ----------- {};docker logs --tail 5 {}"'

パタパタイメージが切り替わるようにやる気なくワンライナー

# set -e
# while : ; do docker service update --image nginx:stable test ; sleep 15 ; docker service update --image nginx test; sleep 15 ; done

数十分放置してみましたが、特に問題ありませんでした。


というわけで元記事に対する半年遅れの回答になりますけどdocker 1.13のswarmモード使えば良いんじゃないでしょうか?

ubuntu 16.04でcloud-initでapt-get upgradeできない件

最近ubuntu 16.04を使い始めたんですけど、簡易プロビジョニングということで、 cloud-initのuser_dataのスクリプトで以下のようにパッケージの更新をしかけていました。

apt-get update
apt-get upgrade -y
# 他、アレコレと……

ところが先週くらいから、コイツが完全にハングするようになってしまいました。(´・ω・`)

原因はsnapdが更新されて、コイツがアップデートの中でsnapd.boot-ok.serviceを起動しようとするんですが、 cloud-initのuser_dataで与えられたスクリプトはシステムが完全に起動する前に実行されちゃうんですね。

ということで、

  • snapd.boot-ok.serviceがmulti-user.targetの起動を待つため、apt-get upgradeが完了しない
  • apt-get upgradeが完了しないから、multi-user.targetがいつまでたっても起動しない

という完全にデッドロックです。\(^o^)/

ソッコーでバグレポートも上がったんですけど、9/16日現在、まだcloud-initでのapt-get upgradeはハングします。 https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1621336

しょうがないので、一旦snapdを更新対象から外してapt-get upgradeするように運用回避してます。

apt-get update
echo snapd hold | dpkg --set-selections
apt-get upgrade -y
echo snapd install | dpkg --set-selections

んー、メンドクセ。

続、workerパターンをcontext化してみたら……

みなさん「みんなのGo言語」は予約ポチりましたか? 私はポチりました!

そんな「みんなのGo言語」の著者の一人であるid:lestrrat さんからの前エントリに対してマサカリが飛んできてます。

workerパターンをcontext化してみたら…… - okzkメモ

context.Contextを揮発性のないものでラップして持つのはよくないと思う。このほうがよりGoっぽいと思うが、どうだろう https://gist.github.com/lestrrat/c9b78369cf9b9c5d9b0c909ed1e2452e

2016/08/23 18:34

コメント内のgistのリンクはこちら

ちょっと時間あいちゃいましたけど、これについて思ったことを2点ほど……

まずはcontext関係ないとこから……

contextの使い方へのツッコミなのにいきなりcontextとは無関係なトコに言及しちゃうのですが、同時実行数の制御の仕方が前回までのコードと異なってるのにモヤモヤしちゃいました。

前回までのコードの方は以下のようなカンジ(以下「worker方式)」で

  1. 同時実行数分のworkerのgoroutineを立ち上げる
  2. その中で、ループでchannel経由で回ってきたjobを処理する

んで、頂いたgistの方は、こんなカンジ(以下「セマフォ方式)」

  1. job分goroutineを立ち上げておいて
  2. セマフォで同時実行数を制限する

元々の「goroutineを爆発させないため」という記事の流れもあったんでworker方式だったというのもあるんですけど、 実際どちらの実装方式も選べるなら、個人的には以下のような理由でやっぱりworker方式を選ぶような気がします。

  • なんとなくセマフォ方式よりworker方式の方が可読性が高い気がする
  • goroutineのそのもののコスト(どんなに軽量でもゼロではない)

可読性は個人の主観によるのでどうしても宗教論争になっちゃいますけど、後者の実行コストの方を検証してみましょう。

というわけで、簡易なベンチマークを用意してみました。

package main

import (
  "sync"
  "testing"
  "time"
)

const concurrency = 4

func BenchmarkLimitByWorkers(b *testing.B) {
  ch := make(chan struct{}, 10000)

  wg := sync.WaitGroup{}
  wg.Add(concurrency)
  for i := 0; i < concurrency; i++ {
    go func() {
      for range ch {
        // 何かしら処理の代わり
        // ベンチマークそのものに支配的にならないようにマイクロ秒だけsleep
        time.Sleep(time.Microsecond)
      }
      wg.Done()
    }()
  }

  for i := 0; i < b.N; i++ {
    ch <- struct{}{}
  }
  close(ch)
  wg.Wait()
}

func BenchmarkLimitBySemaphore(b *testing.B) {
  ch := make(chan struct{}, concurrency)

  wg := sync.WaitGroup{}
  wg.Add(b.N)
  for i := 0; i < b.N; i++ {
    go func() {
      ch <- struct{}{}
      // 何かしら処理の代わり
      // ベンチマークそのものに支配的にならないようにマイクロ秒だけsleep
      time.Sleep(time.Microsecond)
      <-ch
      wg.Done()
    }()
  }
  wg.Wait()
}

手元で実行してみた結果は以下のようなカンジ。

BenchmarkLimitByWorkers-4         300000              4872 ns/op
BenchmarkLimitBySemaphore-4       200000             12835 ns/op

想像どおりセマフォ方式の方がオーバーヘッドが大きいようです。
……が、結局のトコ無視できるくらいですね。

そんなわけでよっぽどパフォーマンス重視のトコ以外は可読性で判断すればいいという結論なんですけど、みなさん、どちらがお好みでしょうか?

contextの揮発性について

えーっと、そもそもなんですけど、contextって揮発性が求められるんですっけ???
極端な例でいうと、context.Background()で帰ってくるcontextなんかは全然揮発しないpackageのvarで定義されちゃってますけど……

さてさて、contextの用途的に、

  • Dispatcher全体のcontext
  • その処理の一部であるjobの処理のcontext

という流れで派生関係があるというのは、個人的には違和感ないですし、その派生関係を利用してDispacher全体をキャンセルさせるStopImmediately()みたいな実装もできたわけで……
# というかあの例はStopImmediately()のためにcontext対応したようなもんですケド(;・∀・)

んでもって、Start/StopみたいにライフサイクルがはっきりしているDispatcherのようなモノのcontextは、自身で(ラップして)管理させるほうがキレイなんじゃないかなぁと思います。


とはいえこういう規約もあるわけで、判断に悩むトコではあるんですけどね……

// Do not store Contexts inside a struct type; instead, pass a Context
// explicitly to each function that needs it. The Context should be the first
// parameter, typically named ctx:
//
//  func DoSomething(ctx context.Context, arg Arg) error {
//    // ... use ctx ...
//  }

でもこれはfunctionで使うcontextの渡し方に限定した内容だと思えば、Dispatcherが内部にcontextを保持するのは矛盾しないかなぁ……と思ってみたり。

い、いかがでしょうか???

……やっぱり、struct typeでラップするのは、、、ダメですかね???

workerパターンをcontext化してみたら……

はい、というわけで、前記事のworkerパターンをcontextつかったらどーなるか、についてです。
前の記事や、その元記事のソースを読んでいる前提ですので、未読の方はそちらの確認からお願いします。

さて、ざっくりとした変更の方針ですけど、以下の2点です。

  • contextを使うことで、workerの実装側でキャンセルできるようにする
  • workerに渡す値はcontext経由にする。

というのをヤッツケでやってつくってみました

以下変更点の解説です。

まず、元実装ではworkerで実行される処理がベタ書きだったのを汎用化するため、dispatcherのqueueに入れるjobをとqueueの定義を変更します。

type (
  job struct {
    proc func(context.Context)
    ctx  context.Context
  }

  Dispatcher struct {
    queue chan *job
    // ...
  }
)

workerの実装も合わせて変更します。

go func() {
  defer d.wg.Done()
  for j := range d.queue {
    j.proc(j.ctx)
  }
}()

さらにdispatcherは親contextとCancelFunc持つようにします。

type (
  Dispatcher struct {
    // ...
    ctx    context.Context
    cancel context.CancelFunc
  }
)

そして使う側がcontextを拡張して使えるようにcontextを返す関数を用意します。

func (d *Dispatcher) Context() context.Context {
  return d.ctx
}

job登録もcontextを使わないパターンと使うパターンで2種類用意します。
使わないパターンでは、dispatcherのctxをそのまま渡します。

func (d *Dispatcher) Add(proc func(context.Context)) {
  d.queue <- &job{proc: proc, ctx: d.ctx}
}

func (d *Dispatcher) AddWithContext(proc func(context.Context), ctx context.Context) {
  d.queue <- &job{proc: proc, ctx: ctx}
}

終了処理も変更しています。

Immediatelyな実装の方では、queueの読み捨てをやめてcontextのcancelを先に呼ぶようにしました。
ちゃんとImmediatelyに帰ってくるかはjobの実装次第ですが、読み捨てよりはお行儀が良い気がしてます。

func (d *Dispatcher) StopImmediately() {
  d.cancel()
  close(d.queue)
  d.wg.Wait()
}

Immediatelyじゃない実装もworkerが全部終了した後ですが、cancel()を実行しています。コレをしないとリークが発生します

func (d *Dispatcher) Stop() {
  close(d.queue)
  d.wg.Wait()
  d.cancel()
}

さて、ここまでやったので、元のgetをcontextを受け取って使うようにちょちょいっと修正して……

func get(ctx context.Context) {
  url := ctx.Value("url").(string)
  req, err := http.NewRequest("GET", url, nil)
  if err != nil {
    log.Fatal(err)
  }

  resp, err := http.DefaultClient.Do(req.WithContext(ctx))
  // ...
}

jobを登録するトコをこんなカンジで、context.WithValueでurlを渡すように修正します。

  for i := 0; i < 10; i++ {
    url := fmt.Sprintf("http://placehold.it/%dx%d", i, i)
    ctx := context.WithValue(d.Context(), "url", url)
    d.AddWithContext(get, ctx)
  }

こんなカンジでworkerパターンのcontext対応化が完了しましたー。

感想

httpのclientとかはcontextに対応していて、中断処理に対応したモノをさっくり作ることができてラクですねー。


(8/24 追記)
id:lestrrat さんからマサカリが飛んできております((((;゚Д゚))))ガクガクブルブル

workerパターンをcontext化してみたら…… - okzkメモ

context.Contextを揮発性のないものでラップして持つのはよくないと思う。このほうがよりGoっぽいと思うが、どうだろう https://gist.github.com/lestrrat/c9b78369cf9b9c5d9b0c909ed1e2452e

2016/08/23 18:34
b.hatena.ne.jp

コチラについて、別エントリを書くかも……

Re: golang の channel を使って Dispatcher-Worker を作り goroutine 爆発させないようにする

こちらを読みました。

blog.kaneshin.co

channel自体にdispatch機構があるからもっとシンプルに書けるのでは?と思って書き直したのがこちら

コードだけぶん投げてもアレなので、あとで解説書きます。

ついでに「go1.7で標準化されたcontext使ったらどうなるか」も気力次第で書くかもしれません。

というわけで追記というか、本編というか、解説です。

channel整理

元のコードを読んでいくと、

  1. dispatcherのqueueにjobを突っ込む
  2. dispatcherがidle状態のworkerをpoolから取り出す
  3. 同じ行で取り出したworkerのqueueにjobを渡す
  4. workerがjobを受け取って処理する

というカンジの処理の流れになってるんですが、dispatch機構自体がchannelにはあるので、

  1. dispatcherのqueueにjobを突っ込む
  2. workerがjobを受け取って処理する

とするだけでOKです。

ここまでで、idle状態のworkerを保存するpoolとworker毎のqueueのchannelが不要になりました。

終了処理

終了処理用にわざわざ別でquiteというchannelを使っていますが、このくらいシンプルなケースではchannelをcloseするだけでOKです。

worker側も単純にforループ回すだけになります。 ループはchannelがcloseされて空になった時点で抜けてくれます。

ここまででworkerが単なるgoroutineになりました。
また、queueをcloseすれば最終的に全workerが終了するので、dispatcherがworkerのリストを保持する必要もなくなりました。

Wait??

元コードのWaitは「queueにつっこんだjobの処理が全部終わって完全にidleになるまでブロックする」というコードになってます。

main()で一回しか呼ばれていないのでなんともいえないのですが、このWait()の実装意図が以下のように「ループで登録されたモノが全部処理されるまで待ち合わせたい」ものだとこの実装ではマズイです。

func run(d *Dispatcher) {
    for i := 0; i < 100; i++ {
        url := fmt.Sprintf("http://placehold.it/%dx%d", i, i)
        d.Add(url)
    }
    d.Wait()
    // 上でAddした処理が全部終わったらナニカしたい!!!
}

このrun()が並列でよばれると、自分で登録したモノだけじゃなく、他ので登録されたモノ全部が終わるまで待ってしまうからです。 この場合はrun()内で、個別にWaitGroupを作ってあげて、ゴニョゴニョしてあげる必要があります。

また、もしかするとWait()の実装意図は単に終了処理用で「全部のjobの処理が終わるまで待ちたい!」というモノかもしれません。 その場合はStop()に統合しちゃえばいいわけですし、書き直した方ではqueueをcloseした後、全部のgoroutineが処理を終えるまで待つようにしています。

Stop??

元コードは引数でimmediatelyかどうかを区別していますが「Stop()にboolな引数がある」というのが個人的には驚き最小ではないのでStop()とStopImmediately()でメソッド自体分けてしまってます。
こうしておけば「Stop()があってStopImmediately()があるということは、StopImmediately()はきっとなにか通常ではやらないことをするんだろうな」と身構えることができるので。

んでImmediatelyな実装の方は「queueにつっこまれたけど処理しないjob」が発生するというモノなんですけど、それを単にqueueから読み捨てるというカタチで書きなおしています。
ただし、workerで処理中のモノがあればその分だけは処理完了するまで待ち合わせます。

元コードが処理中のモノは終了処理用のchannelに書き込むだけで、処理中のモノの完了を待ち合わせていない(!?)ので、その点は挙動が異なっています。

余談ですけど、値に意味の無い終了処理用のchannelは、ダミー値を書き込むよりもcloseした方がいいです。
readしようとしているgoroutineが一つだけだったらいいんですけど、複数あったらその分書き込まなきゃいけませんから。

// コレよりも
w.quit <- struct{}{}
// こっちがベター
close(w.quit)

感想

書きなおしてみると、ただのworkerパターンになっちゃいました(;・∀・)
dispatcher周りのサンプルとしてなら、pub/subパターンの方を実装した方が良かったんじゃないかなぁ……

あと、これにcontextを使うと、標準的な方法でworker側で処理の中断制御ができるようになる……というハナシはまた後日で。

Re: Dockerに載せたサービスをホットデプロイする

こちらを拝見したところ、やりたいコトはdocker1.12のswarmモードで解決するんじゃないかなー、と思ってみたので試してみたテスト。

h3poteto.hatenablog.com

とりあえず、最新版のdocker(1.12)をインストールです。
手元の環境はCentOS7なので、インストールガイドに従ってゴニョゴニョと。

んでもって、swarm初期化。

# docker swarm init

今回1台だけなので、ノード追加は行いません。

次に実験用にnginxのサービスを立ち上げます。
ポイントはimage差し替え時に「停止→起動」の順番で動くので、あらかじめレプリカ数を2にしておいて、ちゃんとローリングで切り替わるようdelayを設定することです。

# docker service create --update-delay 20s -p 80:80 --name test --replicas 2 nginx

この状態でアクセスすると、オーバーレイネットワークでラウンドロビンしながら各コンテナが応答してくれます。

imageの更新は以下の様なカンジです。

# docker service update --image nginx:stable test

swarmがローリングでアップデートしてくれます。
応答も途切れません。インストールもdocker最新版だけでいいので最小限だし、オペレーションも簡単!やったね!


……のつもりだったんですけど、何度もservice updateを繰り返しているウチにレプリカ2コのウチ片方にアクセスが寄ってしまうという現象が発生してしまいました。
当然その状態で寄ってる方から停止すると、無応答時間が発生するわけで……(´;ω;`)ブワッ

再現したりしなかったりで、今のとこ原因特定には至ってません(;・∀・)
なにか、私の設定がマズイのかなぁ……