re:annkara

日々学んだことを書き留めておく。

LINE DEVELOPER DAY 2019 DAY2 参加メモ

DAY2の参加メモです。

Armeria:どこでも役立つマイクロサービスフレームワーク

ArmeriaというLINE社が開発しているOSSのマイクロサービスフレームワークについてのセッション。

line.github.io

nettyプロジェクトの創設者が開発に携わっていることもあり、nettyベースのフレームワークとなっている。
ドキュメント見る限りではシンプルだけどもマイクロサービスに必要な機能をフレームワークとして提供している。

フレームワークの機能としてクライアントロードバランサの機能を実装していたり、SSL/TLS終端ができたりするのでリバースプロキシレスを謳っているのも特徴的。

ドキュメントもしっかりしているので、入門してみたいと思う。

LINE Loginで始めるAuth0の使い方 - 認証基盤をかんたんに構築する方法

LINE LOGIN + Auth0の紹介セッション。

LINE LOGIN

LINE LOGIN は様々なチャネルにLINEアカウントでログインできるようになるサービス。
自社サービスのログインにLINEアカウントでログインできるようになったりする。

Auth0

Auth0は認証・認可のIDaaS。
LINE LOGINもAuth0で利用できるようになっている。

認証・認可の機能はアプリケーションを提供するうえで必須の機能ではあるものの、サービスのコア機能ではない。

また仕組みや実装自体が難しいので他のサービスを利用するというのは十分ありだと感じた。

LINE Pay かんたん本人確認の仕組みとLINEの金融関連サービスとのシナジー効果

LINE上で本人確認できる仕組みに関するセッション。

本人確認(KYC)とは何か

KYC(Know Your Customer )とは、企業がクライアントに対して安全で問題のない人だと確認するプロセスのこと。

銀行口座を開設する際にIDなどを提出して、その人が問題ないか確認するプロセスのこと。

LINEにおけるKYC

今まで物理的なIDなどを提出し人手による本人確認を行っていたものを、スマホで顔写真+身分証明書の写真・動画を送付し、確認プロセスを自動化する仕組みを構築。

金融業を営む上では本人確認プロセスというのは避けられない業務プロセスであるらしく、その業務プロセスを技術で改善していくのは流石だと思いました。

既存の企業が営む業務領域を、技術で再実装していくのは面白いプロセスだろうなとも感じました。

まとめ

全体を通して非常に刺激あるカンファレンスでした。

運営の方をはじめ、本カンファレンスに関わったすべての人に感謝します。

LINE DEVELOPER DAY 2019 DAY1 参加メモ

LINE DEVELOPER DAY 2019 DAY1に参加してきました。
DAY2も参加したので、分けて書こうと思います。

スライドは以下のリンク先に公開されています。

Presentations by LINE DevDay 2019 - Speaker Deck

Keynote

LIFE with LINE

私のLINEの利用経験としては単にメッセージサービスくらいにしか利用していないのですが、提供しているサービスの幅広さに驚きました。

金融分野からeコマース、エンターテイメントなど人が生活する上で必要になる領域において、LINEというプラットフォームをベースに様々な分野でサービスを提供していることを改めて気づきました。

Natural Experience with AI

様々なサービスを提供しているLINEですが、そのサービスの中心になりつつあるのがAI技術である印象を強く持ちました。

AI技術を活用することでユーザ体験を向上させることに繋げ、サービスをより利用してもらうようにする。

そのためには必要なデータを必要な時に扱えるようにデータプラットフォームの整備や、サービスの開発ライフサイクルに合わせたインフラ基盤の提供、データに対するセキュリティやプライバシー保護などの仕組みを整えている。

ビジネスとテクノロジが上手く融合していて、こういう企業で働けると楽しそうだなという印象を持ちました。

特に、データプラットフォームの構築や柔軟なインフラ構築を実現するためにプライベートクラウドを自前で構築することでビジネスや開発者に貢献していくという点が非常に良いなと思いました。

プライベート Kubernetes クラスタにおける gRPC サービス開発

LINE LIVEという動画配信サービスの開発にプライベートクラウド上のKubernetesを導入した話。

Kubernetesを利用する理由

Kubernetesが必要になった理由として、サービスのトラフィックが増加した際に容易にシステムをスケールアウトできることが必要であった。 手動によるスケールアウト/スケールインを行っていたが運用負荷が高くなってしまっていた。

LINE内ではKubernetes Clusterの提供までがインフラ側の役割であり、アプリケーション側ではNamespaceの作成からPodのデプロイ、ロギング・メトリクスを収集する環境までを行う。

アーキテクチャ

サービスとしては、Envoy + SpringBoot + gRPC を組み合わせたマイクロサービスアーキテクチャで構成されている。

EnvoyはL7ロードバランサとしての役割 + プロトコルコンバータとして外部からのリクエストをgRPCプロトコルへと変換する役割を果たしている。

Istioの導入を避けた理由として、メトリクスの収集や分散トレーシング、サーキットブレーカやリトライパターンの実装などはSpringが提供する機能で賄うことができたり、レイテンシの低下を懸念して避けたとのこと。

Istioのパフォーマンスに関しては以下を参照。
Istio / Performance and Scalability

DBはプライベートクラウド内でマネージドなDBサービスを利用しているが、このDBにアクセスするためには接続元サーバのIPアドレスACLに登録する必要がある。

Kubernetesのノードスケールと相性が悪かったため、スケール時にフックしてACLにスケールしたサーバのIPアドレスを追加する仕組みを実装している。

メトリクス・ロギング・アラート

メトリクスの収集にはPrometheusを利用しており、収集したメトリクスは自社内で開発しているTSDBに格納している。

ログ収集には Fluentd + Elasticsearch + Kibana を利用しており、アラートなどは IMON と呼ばれる自社で開発しているツールを利用して検知・通知を行っている。

気になる技術

  • HTTP/2
  • gRPC
  • Envoy
  • Spring Boot Actuator
  • OpenCensus

どれも言葉としては知っているけど、深く調べたり実際に触ってみたわけではないので学習したい。

LINEが開発した時系列データベース‘Flash’の紹介

前のセッションで触れられていた時系列データベースに関するセッション。

Observabilityとは何か

システムの出力からそのシステムの内部状態がどれほど健全か推測できるよう計測を行うこと。
LINEのObservability Teamの役割はサービスが健全であるように手助けをすること。

そのためにメトリクス・ログ・分散トレーシングの集約を実現するインフラ基盤を提供している。

時系列データベースを実装するモチベーション

この数年で収集するメトリクスが劇的に増加した。
メトリクスの保存するストレージは当初はMySQLやOpenTSDBを利用していたが、障害が発生するたびに運用でカバーしていた。

OSSや商用製品の利用も検討したが、以下に述べる要件やコスト、カスタマイズ性の欠如のなどもあり自前で実装することにした。

時系列データベースにおけるLINE社の要件

  • 様々なプロトコルに対応していること
    • HTTP
    • UDP
    • Prometheus Protocol
  • Read/Writeの両方でスケールアウトできること
    • 10xのメトリクスをサポートできること
    • 低レイテンシの実現(Read/Write < 100msを99パーセントタイルで実現)
  • 低コストかつ容易にメンテナンスできること

実現したこと

  • 400万件/秒の書き込みを実現
  • 1000クエリ/秒を捌く
  • Read/Writeのレイテンシを100ms以内に(99パーセントタイル)

アーキテクチャ

  • 開発言語はGo言語を採用
  • 全てのコンポーネントを自前で実装することは避けた
    • バックエンドのストレージにはCassandraを採用
    • ラベルストレージにはElasticsearchを採用
    • Walログ??RaftログにはRocksDBを採用
  • マイクロサービスアーキテクチャを採用
    • 送信元を判別する情報と実際のデータを保管するストレージを分離
    • 分離したことでデータの保持方法を最適化できた
  • 直近28Hのデータをインメモリに保持する
    • 直近のデータはインメモリに保持
      • メモリ効率のために Delta-Delta XOR algorithmを採用
      • 水平スケールのためハッシュ値を元にデータを分散
        • RocksDBはここで利用されている??
    • それ以降のデータは永続化ストレージに保管(上記のCassandra)

プロトタイプ開発は2ヶ月ほどで終えたが、プロダクションレディにするためには1年ほどかかったそうだけど、それでも1年で実装したのは凄い。

@yuuk1tさんと登壇者の@dxhuyさんの以下のやりとりも非常に面白い。

LINE NEWSの記事配信を支える技術

LINE NEWSは2013年にローンチされたサービスであり、当初はネイティブアプリとしてリリースされていたが、2017年にLINEアプリに統合された。
ピーク時には秒間375Kのリクエストを受け付けるため、相当なトラフィックが発生していると思われるサービス。

アーキテクチャ

PerlJavaが併用されているらしく、Javaへ移行しつつあるそう。

Personalized Recomendation

LINE NEWSは各ユーザの特性に応じたニュースをお勧め記事として配信している。

機械学習によって作成されたお勧めニュースの配信をする場合と、人手によるお勧めニュースの配信をする場合の二通りがある。

機械学習によって作成される配信記事は、機械学習チームによって全ユーザ(1億人)毎に200記事を作成しファイル形式で提供され、1時間ごとに更新される。
人手による配信の場合、特定のクラスタごとに配信するニュースを決定する。

機械学習によるお勧めニュース配信においては、記事のリストを保持するストレージにRedisを利用している。 ニュース記事本体のデータはMySQLに保持しているが、キャッシュサーバとしてRedisを利用している。

人手による配信の場合、お勧め記事のリストのストレージには機械学習の場合と同様、 Redisを利用しているがニュース記事の本体のストレージにはCentral Dogmaを利用している。

Central Dogma

line.github.io

LINE社が開発しているOSSで、バージョン管理 + 設定ファイル管理&通知システムみたいなもの。
アプリケーションの設定ファイルを管理し、変更があった場合にはその設定を利用しているアプリケーションに通知を行い、アプリケーションの再起動をせずに設定を変更することができる。

Central Dogmaの利用

この仕組みを利用してニュース記事をCentral Dogmaに格納し、アプリケーションはCentral Dogmaから記事が更新されたことを受けアプリケーション内にキャッシュすることで、ストレージにアクセスすることなく記事を配信することが可能となる。

一時間ごとの更新頻度をリアルタイムに近づける試みなども行っており、課題に対して積極的にシステムを変更したり、改善したりしていたことが印象的でした。

気になる技術

  • Central Dogma

Kubernetesの利用・普及、その先は何か?

ゼットラボ株式会社の代表取締役である河宜成さんのセッション。

設立当初のミッションとして「イケてるモダンなインフラを構築する」ということもあり、DockerやKubernetesなどの技術がリリースされてから直ぐに取り組んできた。

Yahoo!社向けにKaaS(Kubernetes as a Service)を提供しており、Yahoo!社のエンジニアに対して積極的なサポートを行っていることもあり、2017年では5クラスタであったが2019年になると400クラスタまで増え、今後も増え続ける想定とのこと。

今後どんなことをやっていくかということを考えるためには、今何が起きているかを知る必要があると仰っていたことが非常に印象的でした。

気になる技術

今後の展望で述べられていた技術・用語。

  • NoOps
  • サーバレス
  • ゼロトラストネットワーク
  • ステートフルなアプリケーションをK8s上で実行する

Reliability Engineering Behind The Most Trusted Kafka Platform

LINE社のKafkaプラットフォームにおける信頼性エンジニアリングに関するセッションでした。

LINEにおけるKafka

LINE社ではKafkaをサービス間のPub/Subであったり、メッセージキューとして利用している。
以下の指標が指し示している通り、莫大なメッセージ・データ量を捌いていることがわかる。

  • Daily Inflow Messages
    • 360Billion
  • Daily In+Out Data
    • 550TB

SLO(Service Level Objective)

SLOとしては以下の通り。

莫大なメッセージを取り扱うプラットフォームにおいて、以下のSLOを達成することは非常にチャレンジングであると同時に卓越したエンジニアリングがなければ実現し得ないと思う。ただ単に凄い。

  • 可用性
    • 99.999%(年間で許容されるダウンタイムが約5.26分)
  • 応答性能
    • < 40ms(99%のメッセージ)

SLOを実現するためにとりくんでいること

SLOの可視化

SLOを可視化するために、SLI(Service Level Indicator)を定めている。
SLIはサービスの可用性・応答性能を数値として計測できなければならない。

  • API Response Time
    • レイテンシの測定(応答性能)
  • API Error Rate
    • 可用性の測定

たいていこれらのメトリクスはサーバサイドで取得されることが多いが、それだけでは不十分である。

SLIの精緻化

  • クライアントライブラリは信頼性向上の機能を備えているため、以下を考慮する必要がある。
    • リトライした場合
    • フェールオーバした場合
  • Kafkaにおけるメッセージ配信のレイテンシは以下を含む
    • メッセージリクエストのレスポンスタイム
    • リクエスト時のフェールオーバ・リトライ・メタデータの更新に要した時間
    • メッセージを利用する側のリクエストタイム
    • メッセージを利用する側のcoordinationに要した時間(ちょっとここが理解できないけど、分散システムならではの調整時間が要するのだと理解)

Kafkaやライブラリの仕組みを理解したうえで、SLIを精緻化する必要がある。
こういった仕組みを理解し、SLIを詳細に定めたうえで、測定可能なクライアントライブラリを開発し、SLIのダッシュボードを作成している。

Troubleshooting

トラブルシューティングに関しては根本的な原因を究明し、解決することを信条としている。
原因の究明・解決の過程で身につくスキルは、他の同様の障害に応用可能であるし、深い知見を身に着けることができるからとのこと。

障害解析の具体的な例も説明されていたけど、ここでは割愛するけど、JVMシステムコールファイルシステムブロックストレージデバイスまで降りていって障害解析する過程は非常に興味深かった。

SREとしての矜持といったものを感じられて非常に刺激を受けたセッションでした。

【読書メモ】食べる!SSL!

SSL/TLS周りのことをちゃんと理解したくて、積読してあった「食べる!SSL! ―HTTPS環境構築から始めるSSL入門」を読んだ。 セキュリティの考え方や、SSL/TLSの全体像を把握するには非常に良い入門書だったと思う。

以下メモ書き。

セキュリティの考え方に共通する3つの要素

  1. 機密性(Confidentiality)
     情報が意図した相手以外に漏れていないこと
  2. 完全性(Integrity)
     情報が誰にも改ざんされていないこと
  3. 可用性(Availability)
     使用可能であると想定されている状況において使用したいときに使用できること

セキュリティの脅威モデル

情報を何から守るべきか、攻撃者がどのような攻撃を仕掛けてくるかを理解しやすいようにモデル化する考え方。 各脅威の頭文字をとった "STRIDE" という脅威モデルがある。

  1. なりすまし(Spoofing)
  2. データの改ざん(Tempering)
  3. 否認(Repudiation)
  4. 情報漏洩(Information Disclosure)
  5. サービス不能(Denial of Service)
  6. 権限の昇格(Elevation of Privilege)

セキュアな通信を実現するために必要なこと

  1. 「盗聴」されてもデータを第三者に読めないようにする
  2. 「改ざん」されても検知できるようにする
  3. 「なりすまし」されても気づけるようにする
  4. 「否認」できないように、本人しか送付できない情報形式にする

各脅威への対応

  • 「盗聴」への対策
    共通鍵暗号化方式・公開鍵暗号化方式による暗号化により、情報を盗み見られても解読できないようにする。
  • 「改ざん」への対策
    メッセージ認証符号 MAC(Message Authentification Code)の利用し、情報が改ざんされていないか検証を行う。
  • 「なりすまし」や「否認」への対策
    秘密鍵を利用したデジタル署名によって「本人」を特定する。
    サーバ証明書は証明書内に含まれる公開鍵が被証明者のものであることを証明するものである。 この証明にはドメイン証明・法的な実在証明・物理的な実在証明というように証明の信頼度合いが異なる証明方法がある。

SSL/TLSによって保護されているとは

クライアントとサーバ間の通信が暗号化されており、相手のサーバが認証されていて、通信内容が改ざんされた際に、その内容が改ざんされた内容だと受け手が気づくことができるということ。

通信経路上の情報を安全に相手方のサーバへと送信することを目的としており、実際に相手方のアプリケーションが本当に「信頼できる」のか、サーバ側に到達し復号化された情報の安全性などは、SSL/TLSでは保護されない。

SSL/TLSの通信フロー

大きく分けて2段階のフローとなっている。
1. ハンドシェイクフェーズ
2. データ転送フェーズ

ハンドシェイクフェーズ

公開鍵暗号化方式を利用して実際の情報をやりとりするまでの準備を整えるフェーズ。

ハンドシェイクフェーズの目的としては以下の3つ。
1. 暗号化アルゴリズムの合意
2. 認証
3. 暗号化に利用する「鍵の確立」

以下のアルゴリズムについてクライアント側とサーバ側で合意をとる。

  1. SSL/TLSのバージョン
  2. サーバー認証アルゴリズム
  3. 鍵交換アルゴリズム
  4. データ転送で使用するデータ保護用の共通鍵暗号方式アルゴリズム
  5. データ転送で使用するデータの完全性を確認するためのMACアルゴリズム
  6. 圧縮アルゴリズム

アルゴリズムの合意形成後にクライアント側はサーバ側から送られてきたサーバ証明書の検証を行う。
信頼に足る証明書の場合にデータ転送時に利用する共通鍵・MAC鍵を生成し、クライアント・サーバの両方で生成した鍵の妥当性を検証しハンドシェイクフェーズを終了する。

SSL/TLSを構成するコンポーネント

  1. 証明書
  2. 公開鍵・秘密鍵
  3. 暗号化アルゴリズム
  4. SSL/TLSライブラリ

以上、ざっくりとしたメモ書きなので、個々の詳細に別途書くかも。

食べる!SSL! ―HTTPS環境構築から始めるSSL入門

食べる!SSL! ―HTTPS環境構築から始めるSSL入門

振る舞いから見る Docker プライベートレジストリ のイメージ削除について

オンプレミス上で Docker の プライベートレジストリ を運用している際に、不要な Docker イメージが蓄積されディスクスペースをひっ迫することが目に見えたので、どのように削除するのかを検証してみた。

今回は内部実装の面からではなく、ツールを利用してのイメージ削除後、レジストリ の garbage-collect コマンドを利用した際にイメージを格納しているディレクトリにどのような変化が生じるのかという面から記述していく。

記述中の詳細には深く言及しないので、別の記事で再度まとめたい。

検証環境について

自宅の Ubuntu サーバ上に以下のビルド手順を参考に実行バイナリを生成し、環境構築を実施。
Docker イメージを利用しても良かったのだけど、後々ソースコードを弄りたいと思ったときに面倒だなと思ったのでソースコードからビルドする。

distribution/BUILDING.md at master · docker/distribution · GitHub

初期のディレクトリ構造

バックエンドのストレージにローカルファイルシステムを選んだ場合、設定ファイルにイメージを格納するディレクトリを指定することができる。

指定しなければデフォルトは、/var/lib/registry 領域に格納される。

レジストリの詳しい設定は以下の公式ドキュメントに記載されている。

docs.docker.com

nginx:1.17.3 のイメージを Docker Hub から取得後、ローカルにイメージをプッシュすると以下のディレクトリとファイルが生成される。

annkara@annkara:~/registry/docker/registry$ tree -L 10
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │           └── link
            │   └── tags
            │       └── 1.17.3
            │           ├── current
            │           │   └── link
            │           └── index
            │               └── sha256
            │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │                       └── link
            └── _uploads

v2 ディレクトリ配下に大きく二つのディレクトリが生成される。

イメージの追加

先ほどプッシュした同一のイメージID で別のタグ名を持つ nginx:1.17.13_2 をプッシュし、ディレクトリ構造にどう影響を与えるか見てみる。

annkara@annkara:~/registry/docker/registry$ tree -L 10
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │           └── link
            │   └── tags
            │       ├── 1.17.3
            │       │   ├── current
            │       │   │   └── link
            │       │   └── index
            │       │       └── sha256
            │       │           └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │       │               └── link
            │       └── 1.17.3_2
            │           ├── current
            │           │   └── link
            │           └── index
            │               └── sha256
            │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │                       └── link
            └── _uploads

repositories/nginx/_manifests/tags 配下に 1.17.3_2 のディレクトリ・ファイルが生成されたほかは何も変化がないように見られる。

また、同一のイメージを別のタグ名でプッシュしたせいか、index 配下の sha256 のハッシュ値も 1.17.3 のタグと同一の値となっている。

nginx/1.17.3_2 のマニフェストを削除する

プライベートレジストリからイメージを削除するといった場合、2つの解釈が生じうる。

一つ目はソフト削除といって、指定されたイメージ:タグまたはダイジェストのマニフェストを削除するというもの。
この削除ではストレージ上から実際のデータを削除までは実行しないが、メタ情報が存在しないので docker image pull コマンドでイメージを取得することができない。

二つ目はストレージから不要なイメージの実データを削除するというもの。
ストレージ上からデータを削除するには、後述する プライベートレジストリに実装されている garbage-collect サブコマンドを実行する必要がある。

ここでは一つ目のマニフェストを削除することで、ディレクトにどのように影響を与えるか見てみる。

Docker のレジストリDocker Registry HTTP API V2 を通じて処理をリクエストするが、curl コマンド等を利用するのは面倒なのでgenuinetools/regを利用して、マニフェストを削除する。

github.com docs.docker.com

# reg を利用して 1.17.3_2 を削除
annkara@annkara:~/registry/docker/registry$ reg rm -k 192.168.1.209:5000/nginx:1.17.3_2
INFO[0000] domain: 192.168.1.209:5000
INFO[0000] server address: 192.168.1.209:5000
Deleted 192.168.1.209:5000/nginx:1.17.3_2@sha256:55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20

削除後のディレクトリは以下の通り。

annkara@annkara:~/registry/docker/registry$ tree -L 10
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   └── tags
            └── _uploads

tags 配下の1.17.31.17.3_2ディレクトリの両方が削除されてしまったため、docker image pull コマンドでイメージを取得できない。

annkara@annkara:~/registry/docker/registry$ docker image pull 192.168.1.209:5000/nginx:1.17.3
Error response from daemon: manifest for 192.168.1.209:5000/nginx:1.17.3 not found
annkara@annkara:~/registry/docker/registry$ docker image pull 192.168.1.209:5000/nginx:1.17.3_2
Error response from daemon: manifest for 192.168.1.209:5000/nginx:1.17.3_2 not found

この振る舞いから、同一リポジトリ名で別のタグを指定したとしても、同一のイメージIDに基いたイメージ情報は削除されうる。
しかし、別のリポジトリ名でそれらが同一のイメージIDに基づいていた場合、指定されたイメージの情報のみが削除される。

先ほどのreg rm コマンドは内部では以下の API リクエストを発行している。

DELETE /v2/<name>/manifests/<reference>

つまり、<reference> の値が同一であろうと、<name> の部分が異なれば指定されたもの以外は削除されないということ。

同一イメージID で別のリポジトリを削除した場合

上記で述べた場合を検証してみる。
192.168.1.209:5000/nginx/test:1.17.3192.168.1.209:5000/nginx:1.17.3 のイメージをプッシュし、192.168.1.209:5000/nginx:1.17.3 を削除した場合にどうなるかを見てみる。

annkara@annkara:~/registry/docker/registry$ docker image ls
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
192.168.1.209:5000/nginx/test        1.17.3              ab56bba91343        10 days ago         126MB
192.168.1.209:5000/nginx             1.17.3              ab56bba91343        10 days ago         126MB

2つのイメージをプッシュした際のディレクトリは以下の通り、repositories 配下のディレクトリ構成が異なっている。

annkara@annkara:~/registry/docker/registry$ tree -L 11
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │           └── link
            │   └── tags
            │       └── 1.17.3
            │           ├── current
            │           │   └── link
            │           └── index
            │               └── sha256
            │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │                       └── link
            ├── test
            │   ├── _layers
            │   │   └── sha256
            │   │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │   │       │   └── link
            │   │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │   │       │   └── link
            │   │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │   │       │   └── link
            │   │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │   │           └── link
            │   ├── _manifests
            │   │   ├── revisions
            │   │   │   └── sha256
            │   │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │   │           └── link
            │   │   └── tags
            │   │       └── 1.17.3
            │   │           ├── current
            │   │           │   └── link
            │   │           └── index
            │   │               └── sha256
            │   │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │                       └── link
            │   └── _uploads
            └── _uploads

ここで、reg を利用して、nginx:1.17.3 のマニフェストを削除する。

annkara@annkara:~/registry/docker/registry$ tree -L 11
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   └── tags
            ├── test
            │   ├── _layers
            │   │   └── sha256
            │   │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │   │       │   └── link
            │   │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │   │       │   └── link
            │   │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │   │       │   └── link
            │   │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │   │           └── link
            │   ├── _manifests
            │   │   ├── revisions
            │   │   │   └── sha256
            │   │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │   │           └── link
            │   │   └── tags
            │   │       └── 1.17.3
            │   │           ├── current
            │   │           │   └── link
            │   │           └── index
            │   │               └── sha256
            │   │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │                       └── link
            │   └── _uploads
            └── _uploads

改めてディレクトリ構成をみるとtest/nginx:1.17.3ディレクトリは削除されていないため、リポジトリ名が別の場合には削除されないことがわかる。

garbage-collect の実行

今まではマニフェストの削除のみを扱ってきたが、ストレージ上からの削除を実行してみる。
ストレージ上から実データを削除するには以下のように、garbage-collect というサブコマンドを実行する。

#!/bin/sh

annkara@annkara:~/bin$ export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/home/annkara/registry
annkara@annkara:~/bin$ export REGISTRY_STORAGE_DELETE_ENABLED=True
annkara@annkara:~/bin$ /home/annkara/go/bin/registry garbage-collect /home/annkara/go/src/github.com/docker/distribution/cmd/registry/config-example.yml
nginx
nginx/test
nginx/test: marking manifest sha256:55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
nginx/test: marking blob sha256:ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
nginx/test: marking blob sha256:b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
nginx/test: marking blob sha256:a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
nginx/test: marking blob sha256:22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d

5 blobs marked, 0 blobs and 0 manifests eligible for deletion

上記の実行後のディレクトリの状態は以下の通り。

annkara@annkara:~/registry/docker/registry$ tree -L 11
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       │   └── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
    │       │       └── data
    │       ├── 55
    │       │   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
    │       │       └── data
    │       ├── a6
    │       │   └── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
    │       │       └── data
    │       ├── ab
    │       │   └── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
    │       │       └── data
    │       └── b8
    │           └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
    │               └── data
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   └── tags
            ├── test
            │   ├── _layers
            │   │   └── sha256
            │   │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │   │       │   └── link
            │   │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │   │       │   └── link
            │   │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │   │       │   └── link
            │   │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │   │           └── link
            │   ├── _manifests
            │   │   ├── revisions
            │   │   │   └── sha256
            │   │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │   │           └── link
            │   │   └── tags
            │   │       └── 1.17.3
            │   │           ├── current
            │   │           │   └── link
            │   │           └── index
            │   │               └── sha256
            │   │                   └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │                       └── link
            │   └── _uploads
            └── _uploads

test/nginx:1.17.3 が存在するため、実データは削除されずまだ存在している。
次に、test/nginx:1.17.3 を削除し、gabage-collect を実行する。

annkara@annkara:~/bin$ /home/annkara/go/bin/registry garbage-collect /home/annkara/go/src/github.com/docker/distribution/cmd/registry/config-example.yml
nginx
nginx/test

0 blobs marked, 5 blobs and 0 manifests eligible for deletion
blob eligible for deletion: sha256:22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/22/22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d  go.version=go1.13 instance.id=c488cc07-8cee-4cdc-bbfe-3a6a3df72af5 service=registry
blob eligible for deletion: sha256:55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/55/55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20  go.version=go1.13 instance.id=c488cc07-8cee-4cdc-bbfe-3a6a3df72af5 service=registry
blob eligible for deletion: sha256:a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/a6/a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053  go.version=go1.13 instance.id=c488cc07-8cee-4cdc-bbfe-3a6a3df72af5 service=registry
blob eligible for deletion: sha256:ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/ab/ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3  go.version=go1.13 instance.id=c488cc07-8cee-4cdc-bbfe-3a6a3df72af5 service=registry
blob eligible for deletion: sha256:b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/b8/b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d  go.version=go1.13 instance.id=c488cc07-8cee-4cdc-bbfe-3a6a3df72af5 service=registry

実行後のディレクトリの状態。
blobs 配下のディレクトリは残っているものの、実データが削除されていることがわかる。

annkara@annkara:~/registry/docker/registry$ tree -L 11
.
└── v2
    ├── blobs
    │   └── sha256
    │       ├── 22
    │       ├── 55
    │       ├── a6
    │       ├── ab
    │       └── b8
    └── repositories
        └── nginx
            ├── _layers
            │   └── sha256
            │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │       │   └── link
            │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │       │   └── link
            │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │       │   └── link
            │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
            │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   └── tags
            ├── test
            │   ├── _layers
            │   │   └── sha256
            │   │       ├── 22a7aa8442bf42f645c988589059966cb4bc9ca05b712cea56ae26dcd7477a6d
            │   │       │   └── link
            │   │       ├── a6639d774c21523ed49d1becdfc9af3e6f7bc1814c2ac5c74f1c96d48a311053
            │   │       │   └── link
            │   │       ├── ab56bba91343aafcdd94b7a44b42e12f32719b9a2b8579e93017c1280f48e8f3
            │   │       │   └── link
            │   │       └── b8f262c62ec67f02536f49654de586c022043652bbb6bbf76a8dab1542627a8d
            │   │           └── link
            │   ├── _manifests
            │   │   ├── revisions
            │   │   │   └── sha256
            │   │   │       └── 55e7a6f2bb43e38cc34285af03b4973d61f523d26cd8a57e9d00cf4154792d20
            │   │   └── tags
            │   └── _uploads
            └── _uploads

まとめ

  • イメージの実データを削除するには、マニフェストを削除してから garbage-collect を実行する
  • 同一リポジトリで別タグであったとしても、同一イメージIDの場合にはマニフェストは削除されてしまう
  • リポジトリ名が別名であれば、同一イメージIDの場合でも両方とも削除されることはない
  • プライベートレジストリのクライアントライブラリ・CLIツールが少ない
    heroku/docker-registry-clientがあったけど、今はメンテナンスされていなさそう。
  • 実装にふみこんで、ハッシュ値など詳細についてまとめていきたい

ISUCON9 振り返り

go言語を初めて一月ほどのコンビで初のISUCONに参加してきました。
自分がインフラ周りを担当して、もう一人がアプリ担当という役割分担でしたが最終的に342位でフィニッシュでした。

やったこと

  • サーバの複数台構成
    nginx&web(1台) + MySQL(1台) の2台構成という中途半場な形ではあるものの、複数台構成を手間取らず実装できたのは良かった。
    本当は、nginx(1台) + web(1台) + MySQL(1台) というそれぞれ専用サーバとして稼働させることを試してみたのだけど、403 {"error":"IP address is not allowed"}というエラーが発生し、時間をかけても解消できなかったので、2台構成に戻してしまった。
    discord の感想戦見てると、同じような現象が発生している人もいたんだけど、あれは何がいけなかったんだろう。。。

  • netdata/alp の導入
    サーバメトリクスの収集として、ぶっつけで netdata をインストールしてリソースの可視化を行っていた。
    また、nginx のログフォーマットを TSLV に変えて、alp で解析できるようにしていた。
    このおかげで最初はDBサーバのCPUリソースがかなり消費されているなというのが把握できたし、エンドポイント毎の応答時間なども把握できた。
    ただ把握できただけで、アプリケーションの修正に繋げられなかったのが悔やまれる。。。

  • nginx の設定変更 gzip 圧縮の有効化や、HTTP2の設定を入れてみた。
    正直効果あったかといわれると、たぶんない気がするし、HTTP2の設定を有効にすると画像ファイルの取得に404が返ってきてしまったので速攻で戻した。

やりたかったこと

  • デプロイフローの整理
    ビルド&デプロイをすべて手動でやってしまってたので、無駄な時間が発生していたと思う。
    ここらへんは完全に準備不足。

  • マニュアルの理解
    アプリケーションの特性だったり、スコアの算出方法だったりをちゃんと把握すべきだったと思う。
    キャンペーンの仕組みや、外部APIの存在などさっと把握したくらいだったので、それがスコアにどのように影響するかちゃんと理解しておくべきだった。

  • アプリケーションの修正
    N+1 問題の解消は最低限しておきたかった。
    この辺は経験値が不足している。

  • DBにインデックス貼る
    /initialize にアクセスのたびに、DBの初期化が走っていたので、スクリプトを直してインデックスを貼るなどをちゃんとしておきたかった。。。

まとめ

反省する点は他にもたくさんあるけど、とにかく参加して良かったし、非常に楽しかったです。
ISUCON での悔しさは、ISUCONでしか返せないので、来年はレベルアップしてリベンジしてやりたいと思います。

kubectl plugin マネージャの krew を Windows 10 で利用するときの注意点

kubectl plugin 機構について前回のブログにまとめていた際に、プラグインマネージャである krew について触れてなかった。

なので触ってみようと思って、インストールしてみたらちょっとハマったところがあったので、krew のことをざっくりと触れつつまとめおきたいと思う。

reannkara.hatenablog.com

krew とは

kubectl plugin を管理するプラグインマネージャのことで、kubectl plugin 機構を利用して Go で実装されている。

GitHub - kubernetes-sigs/krew: 📦 Package manager for kubectl plugins

kubectl krew help で実行可能なコマンドを見てみると、プラグインの検索やインストール、削除、krew でインストールされたプラグインの一覧などを見ることができ一通りの操作がサポートされている。

$ kubectl krew help
krew is the kubectl plugin manager.
You can invoke krew through kubectl: "kubectl krew [command]..."

Usage:
  krew [command]

Available Commands:
  help        Help about any command
  info        Show information about a kubectl plugin
  install     Install kubectl plugins
  list        List installed plugins
  remove      Uninstall plugins
  search      Discover kubectl plugins
  update      Update the local copy of the plugin index
  upgrade     Upgrade installed plugins to newer versions
  version     Show krew version and diagnostics

Flags:
  -h, --help      help for krew
  -v, --v Level   log level for V logs

Use "krew [command] --help" for more information about a command.

ハマったところ

krew をインストールするときに Windows の開発者モードを有効にしておくか、管理者権限でインストールコマンドを実行しないと以下のようなエラーが発生する。

$ ./krew-windows_amd64.exe install --manifest=./krew.yaml --archive=./krew.zip
Installing plugin: krew
...
A required privilege is not held by the client.
F0811 16:39:00.732515   23444 root.go:52] failed to install some plugins: [krew]

これは krew 自体の問題というよりは、Windows が開発者モードを有効にしていないと、管理者権限を持たないユーザにシンボリックリンクを作成させないという仕様のためらしい。

具体的には以下の issue で golang/go で議論されていた。

どうやら Windows 10 Creators Update で非管理者権限でもシンボリックリンクを作成できるようになったことに合わせて、Go の方でも対応を入れようという話だったらしい。

話の本旨からはそれてしまうけど、issue を眺めていると、今まで動いてなかったものがいきなり動いてしまい当時の仕様を変更してしまうことへの懸念だったり、セキュリティへのリスクなどいろいろと議論されていて、非常に興味深かった。

結局は、Go はマルチプラットフォームをサポートを目指す言語であり、Windows だけ挙動が異なるのを避けたい思いだったり、別のissueをあげて、この変更の影響に比べたら取るに足らないことだよみたいなことを言ってたりして、マージされていた。

github.com

krew のインストーラとしては以下の箇所で os.Symlink関数 が利用されている。

krew 自体をインストールするときだけではなくて、他のプラグインをインストールするときにもこの実装が利用されるので、プラグインをインストールするときには管理者権限で実行するか、開発者モードを有効にしないといけない。

github.com

まとめ

kubectl plugin や krew も最近出てきたものなので色々と改善する余地があるものの、自分で機能を拡張できたりするのは非常に面白いし自分も何かしらの形で貢献していきたい。

参考リンク

時間があるときに読んでおきたい。 blogs.windows.com

Windows API
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE というフラグを Go の方でデフォルトで有効にした。
docs.microsoft.com

たまたま見つけた、Windows 向けの低レベルなAPI を提供する準標準ライブラリなのかな?? godoc.org

redis-cli を弄って単一の Redis クラスターを構築する

ほとんどないとは思うが*1、シングル構成のRedisクラスタが欲しくなった際に、以下のようにコマンドを実行してもエラーが発生する。

annkara@annkara:~/src/redis-5.0.5/src$ ./redis-cli --cluster create 127.0.0.1:6379
[ERR] Wrong number of arguments for specified --cluster sub command

どうやら、redis-cli コマンド内で引数のチェックをしているようなので、チェックを回避するようにして単一マスターノードのクラスタが構築できるか試してみる。

対象の Redis のバージョンは 5.0.5 を利用しており、ソースコードは公式サイトからダウンロードしてきている。

redis.io

とりあえず、エラーメッセージを出力している個所を探してみる。

2050 static clusterManagerCommandProc *validateClusterManagerCommand(void) {
2051     int i, commands_count = sizeof(clusterManagerCommands) /
2052                             sizeof(clusterManagerCommandDef);
2053     clusterManagerCommandProc *proc = NULL;
2054     char *cmdname = config.cluster_manager_command.name;
2055     int argc = config.cluster_manager_command.argc;
2056     for (i = 0; i < commands_count; i++) {
2057         clusterManagerCommandDef cmddef = clusterManagerCommands[i];
2058         if (!strcmp(cmddef.name, cmdname)) {
2059             if ((cmddef.arity > 0 && argc != cmddef.arity) ||
2060                 (cmddef.arity < 0 && argc < (cmddef.arity * -1))) {
2061                 fprintf(stderr, "[ERR] Wrong number of arguments for "
2062                                 "specified --cluster sub command\n");
2063                 return NULL;
2064             }
2065             proc = cmddef.proc;
2066         }
2067     }
2068     if (!proc) fprintf(stderr, "Unknown --cluster subcommand\n");
2069     return proc;
2070 }

2059行目か2060行目のチェックに該当してエラーメッセージが出力されていそう。
チェック処理についてもう少し理解を深めるために、clusterManagerCommandDef型を見てみる。

2006 typedef struct clusterManagerCommandDef {
2007     char *name;
2008     clusterManagerCommandProc *proc;
2009     int arity;
2010     char *args;
2011     char *options;
2012 } clusterManagerCommandDef;
2013
2014 clusterManagerCommandDef clusterManagerCommands[] = {
2015     {"create", clusterManagerCommandCreate, -2, "host1:port1 ... hostN:portN",
2016      "replicas <arg>"},
2017     {"check", clusterManagerCommandCheck, -1, "host:port",
2018      "search-multiple-owners"},
2019     {"info", clusterManagerCommandInfo, -1, "host:port", NULL},
2020     {"fix", clusterManagerCommandFix, -1, "host:port",
2021      "search-multiple-owners"},
2022     {"reshard", clusterManagerCommandReshard, -1, "host:port",
2023      "from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
2024      "replace"},
2025     {"rebalance", clusterManagerCommandRebalance, -1, "host:port",
2026      "weight <node1=w1...nodeN=wN>,use-empty-masters,"
2027      "timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
2028     {"add-node", clusterManagerCommandAddNode, 2,
2029      "new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
2030     {"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
2031     {"call", clusterManagerCommandCall, -2,
2032         "host:port command arg arg .. arg", NULL},
2033     {"set-timeout", clusterManagerCommandSetTimeout, 2,
2034      "host:port milliseconds", NULL},
2035     {"import", clusterManagerCommandImport, 1, "host:port",
2036      "from <arg>,copy,replace"},
2037     {"help", clusterManagerCommandHelp, 0, NULL, NULL}
2038 };

clusterManagerCommandDef は構造体で、2014行目で定義されている clusterManagerCommands 配列を見てみると、Redis クラスタを操作するコマンド名や関数ポインタ、引数の数などを保持するための構造体であることがわかる。

arity という英単語を初めて見たのだけど、関数が受け取る引数の数の事を言うらしい。今回は create コマンドなので、-2 となっている。
改めて条件文にもどってみると、2060行目のチェック処理に該当することがわかる。 つまり、Redis クラスタを構成する際には、3台以上のノードの IP:PORT 番号が必要となる。クラスタと言っているので当たり前といえば当たり前だが。。。

## redis-cli --cluster create 127.0.0.1:6379 のコマンドを実行するとき
## cmddef.arity = -2
## argc = 1 (create コマンドに渡される引数の数)2055行目の int argc = config.cluster_manager_command.argc の値
2059 if ((cmddef.arity > 0 && argc != cmddef.arity) ||
2060     (cmddef.arity < 0 && argc < (cmddef.arity * -1))) {

なので、1ノードの設定でも通せるように修正し、もう一度コンパイルして、コマンドを実行してみる。

annkara@annkara:~/src/redis-5.0.5/src$ ./redis-cli --cluster create 127.0.0.1:6379
*** ERROR: Invalid configuration for cluster creation.
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 1 nodes and 0 replicas per node.
*** At least 3 nodes are required.

Redis クラスタは最低3台のマスターノードが必要だと至極まっとうなエラーが返される。
どうやら他にもチェックをしている個所があるらしい。。。

4578 /* Cluster Manager Commands */
4579
4580 static int clusterManagerCommandCreate(int argc, char **argv) {

        /* 省略 */

4628     if (masters_count < 3) {
4629         clusterManagerLogErr(
4630             "*** ERROR: Invalid configuration for cluster creation.\n"
4631             "*** Redis Cluster requires at least 3 master nodes.\n"
4632             "*** This is not possible with %d nodes and %d replicas per node.",
4633             node_len, replicas);
4634         clusterManagerLogErr("\n*** At least %d nodes are required.\n",
4635                              3 * (replicas + 1));
4636         return 0;
4637     }

clusterManagerCommandCreate関数できっちりとマスターノード台数のチェックがされているのでこれも回避する。

リトライするととりあえず成功する。

annkara@annkara:~/src/redis-5.0.5/src$ ./redis-cli --cluster create 127.0.0.1:6379
>>> Performing hash slots allocation on 1 nodes...
Master[0] -> Slots 0 - 16383
M: c4ba607158e6288e4a07e19f748dc210be0cd1fc 127.0.0.1:6379
   slots:[0-16383] (16384 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join

>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: c4ba607158e6288e4a07e19f748dc210be0cd1fc 127.0.0.1:6379
   slots:[0-16383] (16384 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

値の設定・取得くらいの簡単な確認しかしてないけど、とりあえず単一マスターのRedisクラスタを構築することができた。

結局自分の環境では3台のマスターノードを準備して、クラスタ構成を構築したので実際には運用してないけど、とりあえずのメモ書きとして残しておく。

*1:私の場合はRedisクライアントがRedisクラスタしか対応しておらず、かつ検証環境でもとりあえず稼働するRedisクラスタがあればよいという状況だった。