kubernetesを使い始めている。かなり良いのだが、それまでの非kubernetesの世界で使っていたconsulとconsul-templateに依存する形で動的な設定変更をしていて、それが常々不満だった。もちろんロードバランスしちゃえばよいようなものはkubernetesのServiceにしてしまってそこにアクセスするようにしちゃえば手間いらずなんだけど、memcachedとかが意外にこのパターンにはまらんなーと思って困っていた。そこで ちょっと手の空いた時にTwitterで以下のようにつぶやいたところ


すぐに返事が返ってきた


お、それくさい!と思った物の、これどこにもドキュメントがないんだよね!そういうわけでとりあえず適当にクラスタを立ち上げてみて遊んでみようと思うのだが、あれこれどうやってGoogle Cloud Platform上で認証とかするんだ… 

ここから僕のyak shavingな旅が始まります。
 

まず何回か検索語句を変えつつ、APIへのアクセス方法を調べる… なるたけ面倒くさくない方法がいい…
何回か目で「Podからkubernetes APIにつなげる方法を探せばいいのか」と思い「access kubernetes api from pod」 で検索したところこちらの記事に遭遇:「How do I access the Kubernetes api from within a pod container

なるほどなるほど、こういうことかー

KUBE_TOKEN=$(</var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" \
      https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME

Kubernetesから立ち上がるコンテナにはなんかキーが入ってるんだな!

ってことはこれを使うコードがどこかkubernetes本体に入っているのでは… ということでkubernetesのソースコード内でgrep grep

k8s-token

あるじゃーん。で、ちょこちょこと見ていくと client.InClusterConfig()ってメソッドがある!これだー。これでClientは作れる。Watch()は*podsが必要なので… (*Client).Pods() を使えば取れるな。でもPodsにはnamespaceが必要… namespace、一応仕様には載ってるけど、実際に使われる値がわからん。

というわけでちょっと前のCLIからAPIを叩くサンプルで探ってみようと思い、適当なコンテナにkubectl exec -it ... /bin/bashでログインしてからコマンドを叩くと… curlがない!当たり前か。で apt-get update && apt-get install -y curl 

今度は動く。けどJSONがでかい!でかい!まじででかいよ!読めないよ!ここはjqの出番… あ、また入ってない。apt-get install jq

で、jqを通すとやっとこんな形のデータになってるのがわかる

{
  "kind": "Pod",
  "apiVersion": "v1",
  "metadata": {
    "name": "memcached-gyz1h",
    "generateName": "memcached-",
    "namespace": "default",
    "selfLink": "/api/v1/namespaces/default/pods/memcached-gyz1h",
    "uid": "88528d2e-4170-11e5-8b7e-42010af0da1e",
    "resourceVersion": "10960",
    ....
}
お、namespace=defaultじゃないか。これでいこう。あとのWatch()に渡す引数とかはkubectl に渡す引数とほぼ同じ。だいたい以下の感じでwatch.Interface作成までいける:

  label, err := labels.Parse("name=memcached") // kubectl ... -l に渡すヤツと一緒
  if err != nil {
    return err
  }
  config, err := client.InClusterConfig()
  if err != nil {
    return err
  }
  client, err := client.New(config)
  if err != nil {
    return err
  }
  w, err := client.Pods("default").Watch(label, nil, "")
  if err != nil {
    return err
  }

よーし、これでw.ResultChan()使えばいけるぞー。で、この後はこのチャンネルから来るイベントを読むだけ。goroutineをひとつこれ専用にしておいて、イベントによって設定を変えるようにしておけば良い感じにできる。大枠では以下のような感じ:

  go func() {
    for e := range w.ResultChan() {
      pod, ok := e.Object.(*api.Pod)
      if !ok {
        continue // We only care about pod events
      }
      switch e.Type {
      case watch.Modified:
        // watch.Addedを見てもまだphaseがRunningとは限らないので、
        // ここで pod.Status.Phase == api.PodRunningになったら何かするのがよいと思う
      case watch.Deleted:
        // podが消えた!削除削除ー
       }
    }
  }()

あとはうまいことこの基本から設定を更新する形にして、動きそうになったらkubectl delete pods -l ... とかで対象のpodを消してみたりするとうまいこと動く(はず)。

ということでここまでできるとconsul/consul-templateがなくてもこの手の自動更新がプログラム内でわりとキレイに書けるよ!って事がわかった。kubernetesの設定に書いてあったconsul系のツールはこれで全部消せるので嬉しい。

kubernetes素晴らしい気はひしひしと感じているけど、正直ドキュメントがもっとあったらなーと思う…
ともあれ、GCPを使った時の「あ、すぐ動くな、これ!」感はkubernetesでもわりとうまく使える感じがしてきた。