Quantcast
Channel: Cybozu Inside Out | サイボウズエンジニアのブログ
Viewing all 698 articles
Browse latest View live

Kubernetesへの機能追加にかかわった話と、そこから得た知見

$
0
0

はじめに

こんにちは、Necoプロジェクトsatと申します。

みなさんはKubernetesに機能が追加されるまでの流れをご存知でしょうか。githubに存在するプロジェクトであれば典型的にはfeature request用のissueが立てられて、それをもとにPRが作られてレビューを経たのちにマージという流れです。しかしKubernetesはたくさんのプロジェクトから構成される非常に複雑なシステムなので、このような単純なやりかたが難しいのです。KubernetesではそのかわりにKubernetes Enhancement Proposal(以下KEPと表記)というしくみを使って新機能を開発するというスタイルをとっています。

本記事はKEPについて簡単に説明した後に、Kubernetesの機能開発が進んでいく具体的な流れについて、NecoプロジェクトがレビューにかかわったKEPを通して紹介します。

KEPとは

Kubernetesに新機能を追加しようとした場合、まずは所定の形式に沿ってkubernetes/enhancementsというプロジェクトへのPRという形でKEPを発行します。まずは機能追加の要否やデザインの良し悪しについて関係者のレビューを受けて、レビューが通れば晴れて実装可能な状態となります。

KEPの役割はこれで終わりません。みなさんはKubernetesがリリースされるたびに「〇〇機能がbetaからGAになった」などの文言を見ることがあると思いますが、各機能のalpha,beta,GAという成熟度もKEPを介して更新されます。

kubernetes/enhancementsを見ると現在Kubernetesにどんな機能が提案されているのか、それぞれの開発状況はどうなっているのか、などの情報が得られるので、興味のあるかたはフォローしてみるとよいでしょう。

本記事ではKEPそのものについてのしくみについてはこれ以上述べませんが、以下の記事、およびそこから参照されるドキュメントが参考になるので、ご興味のある方はごらんください。

qiita.com

KEPにかかわるようになったきっかけ

NecoプロジェクトはTopoLVMというローカルストレージ用のCSIドライバを作っています。TopoLVMはNeco専用の特殊なものではなく、Kubernetesでローカルストレージを扱いたい人であればだれでも恩恵を受けられるものだったので、あるときKubernetesのslackの中でストレージについての話題を扱うsig-storageというチャネルで紹介をしました。これに対してpmem-csiという不揮発性メモリ用のCSIドライバの開発者であるPatrick Ohlyさんというかたが興味を持ってくれたのがすべての始まりです。

彼が注目したのはTopoLVMが持つ、ノードのストレージ空き容量を考慮してPodをスケジューリングするという機能です。この機能については以下記事の"TopoLVMの特徴"という節をごらんください。

blog.cybozu.io

我々がsig-storageでTopoLVMを紹介したとき、偶然にもpmem-csiではちょうど同様の機能を開発中だったのです。

github.com

さらに彼はこの機能をドライバ独自のものではなくKubernetesの標準的な機能として実装したいと考えていたため、以下のKEPを発行していました。

github.com

このような事情から、上記KEPに対して、この機能についての知見を持っている我々のコメントが求められたというわけです。

どのようなKEPか

本KEPの目的は、次のようなことをKubernetesの標準機能として提供することです。

  • 各ストレージシステムが空き容量についての情報をAPIサーバに見える形で提供する
  • スケジューラは上記の情報を参考にして、可能な限りボリュームの割り当てに成功するよう考慮してPodをスケジュールする(さもなくばPodはスケジュールできない状態になる)

本KEPはTopoLVMやpmem-csiなどのノードローカルストレージだけではなく、クラウドサービスにおけるゾーンローカルストレージなど、より多くの種類のストレージシステムに対応できるようになっています。

我々にコメントが依頼された時点でのKEPは次のようなものでした。

  • 以下2種類のボリュームについて考慮する
  • 各ストレージシステムの空き容量を管理するためのCSIStoragePoolというKubernetesのリソースを追加する
  • ephemeral volumeのサイズ情報を標準化する
  • 上記の情報を使ってPodをスケジュールするようスケジューラを拡張する

Ephemeral volumeはなじみのない機能かもしれないので簡単に説明しておきます。KubernetesにおいてCSIドライバが提供するボリュームは通常永続化されており、Persistent VolumeとPersistent Volume Claimという2つのリソースによって管理されています。そしてボリュームのライフライクルはPodとは独立しています。しかし、Podと同じライフライクルを持つボリュームが欲しいというユースケースがあります。Ephemeral Volumeはこのようなときのために使うものであり、Podの生成と同時に作成され、かつ、Podの終了と共に削除されます。

次節から本KEPに対して具体的にどのようなコメントがあって、かつ、それがどのようにKEPに反映されたかについて記載します。

我々によるコメント

前節において述べたKEPに対して我々からいくつかのコメントをしました。そのうちの主だった3つについてここでは紹介します。

User Storiesが弱い

KEPには、新機能によって誰がどのようなことをできるようになるかを書くUser Storiesという項目があります。我々がレビューした当時、ここにはpmem-csiについてしか書かれていませんでした。これでは本KEPによって利益を受けるユーザが相当限られてしまうと思った我々は、TopoLVMのようなローカルストレージやネットワーク経由で接続されたストレージなどをここに追加してはどうかと提案しました。

幸いにもこの提案は受け入れられ、User Storiesは充実しました。

空き容量の大きさによる優先順位付け

本KEPでやろうとしていることは十分なストレージ空き容量が存在するノードにPodをスケジュールするという単純な機能だけでした。その一方で、TopoLVMではこれに加えて空き容量が多いノードにPodを優先的にスケジュールするという機能(以下、優先順位付け機能と記載)も備えていました。この機能には、各ノードのストレージが均等に消費されていくため、Podの分散配置やボリューム作成後のノードのリサイズがしやすいといった利点があります。これを踏まえて我々は優先順位付け機能をKEPに盛り込んではどうだろうかと提案しました。

この提案については、彼から「KEPをできるだけ単純なものに保って、その後に次第によくしていきたい」という回答がありました。この意見には我々も異論がなかったので、提案を取り下げました。

ephemeral volumeのサイズに関するもの

現在のephemeral volumeにはサイズを指定する標準的な方法がなく、サイズ指定をする方法はドライバに任されています。しかし本KEPはephemeral volumeも考慮に入れているため、ephemeral volumeのサイズ標準化(以下簡単のため"サイズ標準化"と記載)が前提条件となります。そこでサイズ標準化も同じKEP内でやってしまおう、というのが当時の状況だったのですが、このやりかたには次のような問題がありました。

  • サイズ標準化は、これ自身でKEP発行に値する独立した機能と考えられる
  • ephemeral volumeの仕様が複雑怪奇なため、仮にこのKEPでサイズ標準化しようとすると影響範囲が巨大化する

我々はこれらの問題について指摘した上で、ephemeral volumeについては本KEPではそもそも対象外としてはどうかという提案をしました。

この提案についてはsig-storageのメンテナでもあるMichelle Auさんの意向もあって、サイズ標準化は別KEPとして分離されました。

github.com

ただしephemeral volumeの考慮は削除されずに残りました。つまり本KEPはサイズ標準化用の新たなKEPに依存するようになったということです。

それ以外のコメント

我々のコメントの前後に別の開発者からも次のような様々なコメントがあり、それらに従ってKEPは洗練されていきました。

  • 他のKEPとの重複排除
  • User Storiesの追加
  • CSIStoragePoolのデータ構造の変更
  • その他矛盾の解消

上記のような過程を経て、PR発行から二か月程度後についに本KEPは実装可能段階になりました。これに伴ってテスト計画成熟度を上げる基準ための定義*2がKEPに追加されました。

その後の流れ

この後は実装を粛々と進めるだけ…と思いきや、そうは問屋が卸しません。ソフトウェア開発の経験が豊富なかたほど想像しやすいとおもいますが、設計を終えた後に実装段階で初めて明らかになる問題というものが多々あります。本KEPもその例外ではなく、実装開始時点からさまざまな問題が噴出し、現在は実装を一時中断している状態になっています。

検出された問題をすべてを詳しく説明していてはきりがないので、ここではそのうち重要なものについて軽く触れておきます。

  • ストレージの都合でスケジューラのコア部分に手を入れるのに強い反対が起きたため、TopoLVMも使っているscheduler extenderなどの他の方法による実現方法を探さざるをえなくなった
  • ephemeral volumeの現状の設計がまずいために、これ以上機能追加するよりも設計段階から作り直すほうがよいのではないかということになった(関連する議論)
  • そもそもPodスケジュール時にボリュームの割り当てに失敗するとにスケジューリング不可能になるバグを修正すべきだということになった(関連issue)

興味のあるかたは関連情報を追ってみてください。

おわりに

我々は本件を通して初めてKEPのプロセスにかかわったことによって、次のような知見を得ました。

  • KEPプロセスそのもの。今後我々自身がKEPを発行する際はそれほど手間取らないはず
  • ephemeral volumeの仕様などの本件に関連する技術
  • Kubernetesの機能追加に必要な能力。関係者を説得するための広範な知識、ステークホルダーを巻き込む能力、およびそれを継続させる精神的なタフさなど
  • KEPによるKubernetesの変更は少なくとも数か月、場合によっては一年以上を要するため、なんらかの機能がすぐに必要なのであればKEP以外の方法で実現するのが得策であること

本記事によって我々の知見がみなさまに一部なりとも共有できたのであれば幸いです。

*1:機能名はドキュメントごとに表記の揺れが激しいのですが、ここでは"ephemeral volume"と記載します

*2:これこれの基準を満たせばalphaからbetaになれる、など


OSSへの貢献ノウハウ

$
0
0

はじめに

こんにちは、Necoプロジェクトsatと申します。本記事は世間で何かと重要といわれつつもなぜ重要なのかがわかりにくく、かつ、広くやりかたが知られていないOSSへの貢献ノウハウについて述べます。本記事は筆者が過去にはLinuxカーネル、現在ではRookというOSSへの貢献に業務で取り組んできた経験に基づいて書きました。

ひとくちに貢献といっても様々な方法がありますが、ここではissue発行やPR発行などのOSSの開発へ開発者が直接かかわるような貢献に焦点を絞ります。

本記事の想定読者は次のようなかたがたです。

  • 業務でLinuxやKubernetes,MySQLなどの有名どころのOSSを使っている
  • バグや機能不足で困っている
  • OSSへ貢献したことがない
  • 貢献する必要性ががわからない
  • 自分では必要性がわかっているが、会社にうまく伝えられない
  • 貢献したいものの、やりかたがわからない

貢献の利点

OSSに貢献する利点はどのようなものでしょうか。「OSSは使っている限りはなんらかの方法で貢献すべきだ!」というようなことが言われることもありますが、そうはいっても具体的に企業としての利益につながらないとなかなか足を踏み出しにくいのではないでしょうか。そこで、ここでは企業の実利という観点でのOSS貢献の利点について書きます。

OSSを使っていて、まったく問題が起きないというような幸せなケースはまずないと思います。必ずといっていいほどバグや機能不足という問題にぶち当たるでしょう。このようなときにまず思いつくのは「OSSなんだからupstream版*1に自社独自修正をしたものを使えばいいじゃないか!」というアイデアです。実際にこのような方法で問題を解決している例は少なくありません。

ただしこの方法には大きな問題があります。それは時間の経過、もっと具体的にいうとupstreamの安定版リリースごとに大きなコストがかかり、しかもそれが段々増えていくということにあります。最初はupstream安定版に自社独自修正を適用するだけでよいのですが、次の安定版が出たときはどうでしょう。このときは古い安定版に、新しい安定版のうちバグ修正などの必要な修正のみをバックポートして、その上に自社独自修正を適用するという手段をとることが多いようです*2。この手間が安定版のバージョンアップごとに増えていくというわけです。

f:id:cybozuinsideout:20200214175928p:plain

このようなことを避けるためにはupstream版に自社の修正をマージするという方法があります。いったんマージされてしまえば、その後に別の人がPRを発行するときは、みなさんのものを含めた既存のコードが正しく動き続けるように保つ必要があります。もちろんみなさん自身にもregressionが起きないようにコードレビューやテストをするコストはかかりますが、独自修正をし続けるよりはトータルコストが少なくなることが多いです。例えば多くのLinuxディストリビューションは修正をなるべくupstreamに取り込むという"upstream first"という方針を採用しています。

f:id:cybozuinsideout:20200214180040p:plain

上記に述べたもの以外にOSSへの貢献には次のような利点があります。

  • 開発者は貢献したOSSについての知見が得られる。以後の問題発生時の対処が迅速になる
  • 顧客に対して「弊社には製品で使用しているAというOSSに熟達した開発者がいます」とアピールできる
  • 社外開発者に対して「AというOSSの仕事ができる会社」として認識され、採用につながるかもしれない*3

貢献方法

はじめの一歩

OSSに貢献しよう…と決めたものの「やったことないのでわからない、社内にノウハウも無い」というかたはたくさんいるのではないでしょうか。このようなときはお使いのOSSに見つけた些細なバグの報告や修正をお勧めします。ドキュメント追加/修正やテスト追加も歓迎されます。とにかく肩肘張らずに簡単なことから始めるのが望ましいです。

さて、貢献ネタは決まったものの、どういう作法でやればいいかがわかりません。これについては大きめのOSSであればDevelopment.mdContribution.mdのような「それっぽい」ファイルに貢献方法が書いていることが多いです。ドキュメントを見た後は他のissue/PRなどを見様見真似でご自身のものも発行してみましょう。見様見真似は後々になって難しいissue/PRに挑戦するときも同様です。

issue/PRの発行には、いくつかコツがあります。第一にプロジェクトの中の多くの人が嬉しくなることをアピールしましょう。たとえば単に「このバグが直らないと弊社のシステムが止まるんです!」とみなさんの都合をまくしたてるよりも、コミュニティの中のみんなが遭遇しうる問題だという言い方のほうが反応してもらえる可能性が高いでしょう。

第二に、issue発行だけではなく、なるべく自分でPRも出しましょう。仮に修正や機能追加の必要性が認められたとしても、それを実行する人的リソースには限りがあるのです。

第三に、issue/PR発行時に最初から肯定的な反応を期待しないようにしましょう。否定的な反応をされても、それは注目されているサインだと受け取って、くじけずにしっかりと議論して説得しましょう*4。否定的な反応よりも困るのが無視です。この場合は、そもそも誰の興味もひけていないので、投稿内容を見直して再チャレンジしてみましょう。

自社が遭遇した問題を解決したい

簡単な貢献に慣れてきたら、次は本題ともいえる自社が遭遇したバグや機能不足などの解決をしてみましょう。典型的な解決までの流れは次のようになります。

  1. 既存issue/PRの有無を確認。あればそこで「自分も遭遇した」と言ったり追加情報を提供したり、レビューしたりといった貢献をする
  2. 無ければ自分でissue/PR発行

PRのマージには数か月、あるいは一年以上かかることもありますし、最悪の場合はマージされないこともあります。このため、常に次のような代替案を考えておきましょう。

  • 回避策を見つける
  • 一時的に自社独自版/開発版を使う
  • 自社独自版を使い続ける(できれば避けたい最後の手段)

たとえば我々は自社インフラで利用するためにRookの開発に取り組んでいますが、現在開発においてはupstreamの安定版ではなく自社独自版を使っています。その理由は、我々の環境で動かすのに必要な機能がまだupstream版にマージされていないためです。この機能は次版にマージされる見通しは立っているために、一時的にという条件付きで自社独自版を使っています。

開発スタイルについて

業務システムでOSSを使う場合は、なるべく開発版ではなく安定したものを使いたいものです。このため、必要なPRがマージされた安定版がいつリリースされるのかの目安を知るのは非常に重要です。このためにはドキュメントや各種コミュニケーションチャネル*5から次のような開発プロセスを確認しておくとよいでしょう。

  • マージ条件
  • 開発版へのマージ可能時期
  • 安定版のリリースサイクル

参考までにRookは次のようになっています。

  • メンテナ*6は4人
  • 新機能マージには3人のレビューが必要
  • 安定版のリリース間隔は未定義
    • これまでの実績は3~6か月に一回
  • 新機能を安定版にバックポートすることもある

さらにその先へ(参考)

自社向けのバグ修正や機能追加にとどまらずに、さらに先を目指すこともできます。それはプロジェクト全体を盛り上げることです。これだけ書くと「何が嬉しいの?」と思われるでしょうし、実際のところ短期的に目に見える形での効果はわかりにくいです。しかし、長期的に見ると大きな効果が期待できます。

単純化すると、プロジェクトが盛り上がると次のような好循環になることが期待できます。

  1. ユーザが増える
  2. 開発リソースが増えてバグ報告/修正、機能追加増加
  3. 機能が充実、品質が向上
  4. 1に戻る

さらに、このようなコミュニティ全体に向けた貢献をしていると、メンテナを含めたコミュニティに信頼されるようになってissue/PRに反応がもらえる確率が高まりやすい、といった効果もあります。

プロジェクト全体を盛り上げるには例えば次のような方法があります。

  • 自社に直接関係が無いissue/PR発行、レビュー
  • ユーザサポート
  • イベントでの発表

貢献時の課題

OSSへの貢献には、これまで述べたもの以外にも、大きな、かつ見過ごされがちな課題があります。ここでは開発者、および開発者を雇用している会社についてのものを紹介します。

開発者の課題

OSSへの貢献はソフトウェアの設計/プログラミングするといった能力(ここでは仮に「技術力」とします)があればできる、というものではありません。技術力が高いのにOSSへの貢献がうまくいかないという例を筆者はたびたび見聞きしてきました。なぜなら、OSSへの貢献には技術力以外にも、以下のような全く別種の能力が必要になるからです。

  • 見知らぬ社外エンジニアに話しかける度胸
  • それぞれ違う目的でOSS開発に参加している関係者との交渉能力
  • 英語力。あるいは英語が下手でも自分の意思を魂で伝えようとする能力

これらに加えて、主要な開発者、あるいはメンテナともなると日本時間深夜の定例ミーティングに参加したり、海外カンファレンスへの頻繁な参加が必要だったりと、業務負荷がかなり高まるというのも見逃せません。

見かたを変えてみると、OSS開発を長年続けていれば上記のようなことは最初はできなくても時間をかければ自然とできるようになってきます。このような能力を身に着けたいかたには、大変ですが魅力的な業務でしょう。

会社の課題

OSSに貢献したい企業は次のような社内制度の変更が必要になることがあります。

  • 勤怠システム: 前述の変則的な勤務形態が存在しないのなら作る
  • 情報発信ルール: Issue/PR発行、あるいはメール一通ごとに上長の許可が必要…といった制約は辛い
  • 法的ルール: 著作権の帰属、CLAへの署名要否など

会社および開発者のために、これらはしっかり準備しておきましょう。

社内制度だけではなく、会社組織そのものに考え方の転換が必要です。何よりも大事なのは「OSSの開発は自社の都合では進まない」ということでしょう。これまでに述べてきたような情報を関係者間で共有して、それを前提として問題解決を目指しましょう。そうしないと開発者は会社とOSSコミュニティとの間で板挟みにして苦しむことになります。以下はその「あるあるネタ」です。

  • PRがなかなかマージされないときに「次の安定版に絶対マージしろ」「マージしてくれるように毎日催促しろ」「いくら払えばいいんだ」などという
  • コミュニティ全体のための活動をしているときに「会社に関係ないことをするな」などという

おわりに

本記事で述べたことをまとめると次のようになります。

  • OSSへの貢献をすると、社会貢献だけではない具体的に嬉しいことがある
  • 肩肘張らずに徐々にできる範囲からはじめるとよい
  • 開発プロセスを知るのは重要
  • 開発者、会社ともに直接的な開発とは別の部分で課題がある

最後に一言。みなさんも会社の垣根を越えて一緒にOSSに貢献してみませんか。我々は情報の出し惜しみはしません!

*1:OSSの公式版といった意味合い

*2:よりアグレッシブに、新しい安定版に自社独自修正を適用するという方法があります。詳細は省略しますがバージョンアップへの追従の大変さは本文に書いた方法と似たり寄ったりです

*3:我々の場合は実際そうなっています

*4:明らかに相手に理があるとわかったときは速やかに引き下がりましょう。論破するのが目的ではありませんし、無理強いは嫌われるだけです

*5:github, slackやメーリングリストなど

*6:ここではプロジェクトのmaster branchへのコミット権限、安定版のリリース権限がある人のことだとします。プロジェクトによってコミッタと言ったりもします

指数関数expのAVX-512によるベクトル化

$
0
0

初めに

サイボウズ・ラボの光成です。

C++で単精度配列に対する指数関数のベクトル化をAVX-512を使って実装しました。 標準関数std::exp(float)に対する相対誤差は2e-6、速度は10倍ぐらいです。 指数関数をどうやって計算するのか、一般的な話とAVX-512に特有の部分を紹介します。

想定読者

C++とx64(x86-64)のアセンブリ言語の知識を多少仮定しますが、 なるべく少ない前提知識で読めるように心がけます。 ある程度知識のある方は近似計算から読み始めてかまいません。

実行環境

AVX-512が使える環境とx64用C++コンパイラが必要です。

コードはherumi/fmathにあります。 コンパイルにxbyakが必要なのでダウンロードして適宜includeパスを指定してください。

fmath2名前空間で

void expf_v(float *dst, constfloat *src, size_t n);

が定義されています。n個のfloatの配列srcのexpをdstに格納します。

for (size_t i = 0; i < n; i++) {
  dst[i] = std::exp(src[i]);
}

と(誤差を除いて)等価です。

ベンチマーク

速度

ベンチマークはexp_v.cppで行いました。

float x[3000];に対してexpを求める計算をstd::expとexpf_vとで比較しました。

環境はOS : Ubuntu 19.10, CPU : Xeon Platinum 8280 2.7GHz, compiler : gcc-9.2.1 -Ofastです。

関数std::expexpf_v
時間(clk)22.6K1.8K

clkはrdtscによるCPUクロックの計測で、概ね10倍以上高速化されています。

誤差

相対誤差を(真の値 - 実装値) / 真の値とします。 std::exp(float x)を真の値としてx = -30から30まで1e-5ずつ増やして計算した値の相対誤差の平均を出すと2e-6となりました。

exp(x)の性質

指数関数exp(x)はe = 2.71828...の巾乗exp(x) = exという関数です。

数学的には

exp(x) = 1 + x + x2/2! + x3/3! + ... + xn/n! + ...

と定義されています。 xはマイナス無限大からプラス無限大までとりえます。

ここで!は階乗の記号で4! = 4 * 3 * 2 * 1です。 定義は無限個の値の和ですが、コンピュータではもちろん途中で打ち切って有限和で近似計算します。

xの絶対値が1より小さいときはxnはとても小さく、更にn!で割るのでもっと小さくなります。 したがって少ない和で打ち切っても誤差は小さくてすみます。 しかしxが1より大きいとxnはとても大きくなり、打ち切り誤差が大きくなります。 どのようにして誤差を小さくするかがポイントです。

y = axの逆関数をx = log_a(y)と書きます。 特にa = eのときlog_e(y) = log(y)と省略します。

  • ax+y = ax ay
  • log(xy) = y log(x)
  • xy = zy log_z(x) ; 底の変換公式

などが成り立ちます。

計算の範囲

まずxのとり得る範囲を調べましょう。 xの型はfloatです(ここではx64が対象なのでfloatは32bit浮動小数点数とします)。 調べてみるとx = -87.3より小さいとfloatで正しく扱える最小の数FLT_MIN=1.17e-38より小さく、 逆にx = 88.72より大きいと最大の数FLT_MAX=3.4e38よりも大きくなってinfになります。 従ってxは-87.3 <= x <= 88.72としてよいでしょう。

近似計算

2の整数巾乗2nはビットシフトを使って高速に計算できます。 したがってexp(x) = exから2の整数巾を作り出すことを考えます。

底の変換公式を使って

ex = 2x log_2(e)

と変形し、x'=x log_2(e)を整数部分nと小数部分aに分割します。

x' = n + a (|a| <= 0.5)

そうするとex = 2n× 2aです。

2nはビットシフトで計算できるので残りは2aの計算です。 ここで再度底の変換をします。

2a = ea log(2) = eb ; b = a log(2)とおく

|a| <= 0.5でlog(2) = 0.693なので|b| = |a log(2)| <= 0.346.

bが0に近い値なのでebを冒頭の級数展開を使って近似計算します。

6次の項は0.3466/6! = 2.4e-6とfloatの分解能に近いので5次で切りましょう。

eb = 1 + b + b2/2! + b3/3! + b4/4! + b5/5!

アルゴリズム

結局次のアルゴリズムを採用します。

input : x
output : e^x

1. x = max(min(x, expMax), expMin)
2. x = x * log_2(e)
3. n = round(x) ; 四捨五入
4. a = x - n
5. b = a * log(2)
6. z = 1 + b(1 + b(1/2! + b(1/3! + b(1/4! + b/5!))))
7. w = 1 << n
8. return z * w

意外と短いですね。

AVX-512での実装

AVX-512はint32_t, int8_t, double, floatなど様々な型のをまとめて処理する命令セットの名前です。 一つのレジスタが512ビットあるのでfloatなら512/32 = 16個まとめて処理できます。 レジスタはzmm0からzmm31まで32個利用できます。

AVX-512の命令概略

この記事に登場するAVX-512用の命令をまとめておきます。 詳細はIntel64 and IA-32 Architectures Software Developer Manualsを参照ください。

アセンブリ言語の表記はIntel形式で、オペランドはdst, src1, src2の順序です。 dst, srcはdst, dst, srcの省略記法です。

命令意味注釈
vmovaps [mem], zmm0
vmovaps zmm0, [mem]
[mem] = zmm0
zmm0 = [mem]
memは16の倍数のメモリアドレスであること
vmovups [mem], zmm0
vmovaps zmm0, [mem]
[mem] = zmm0
zmm0 = [mem]
memの制約はない
vaddps zmm0, zmm1, zmm2zmm0 = zmm1 + zmm2floatとして
vsubps, vmulpsなども同様
vminps zmm0, zmm1, zmm2zmm0 = min(zmm1, zmm2)floatとして
vpaddd zmm0, zmm1, zmm2zmm0 = zmm1 + zmm2uint32_tとして
vpslld zmm0, zmm1, immzmm0 = zmm1 << immuint32_tとして
vfmadd213ps zmm0, zmm1, zmm2zmm0 = zmm0 * zmm1 + zmm2floatとして
vpbroadcastd zmm0, eaxeaxを16個分zmm0にコピーする
vcvtps2dq zmm0, zmm1zmm0 = round(zmm0)結果はint型
vcvtdq2ps zmm0, zmm1zmm0 = float(zmm0)結果はfloat型
vrndscaleps zmm0, zmm1, 0zmm0 = round(zmm1)結果はfloat型

初期化

// exp_v(float *dst, const float *src, size_t n);void genExp(const Xbyak::Label& expDataL)
{
    constint keepRegN = 7;
    usingnamespace Xbyak;
    util::StackFrame sf(this, 3, util::UseRCX, 64 * keepRegN);

StackFrameは関数のプロローグを生成するクラスです。 3は引数が3個、UseRCXはrcxレジスタを明示的に使う指定、 zmmレジスタの保存のため64 * keepRegN byteスタックを確保します。

const Reg64& dst = sf.p[0];
    const Reg64& src = sf.p[1];
    const Reg64& n = sf.p[2];

StackFrameクラスのsf.p[i]で関数の引数のi番目のレジスタを表します。 WindowsとLinuxとで引数のレジスタが異なるのでここで吸収します。

// prolog#ifdef XBYAK64_WIN
    vmovups(ptr[rsp + 64 * 0], zm6);
    vmovups(ptr[rsp + 64 * 1], zm7);
#endiffor (int i = 2; i < keepRegN; i++) {
        vmovups(ptr[rsp + 64 * i], Zmm(i + 6));
    }

AVX-512のZmmレジスタを保存します。 関数内でWindowsではzmm6以降、Linuxではzmm8以降を利用する場合は保存する必要があります。

// setup constantconst Zmm& i127 = zmm3;
    const Zmm& expMin = zmm4;
    const Zmm& expMax = zmm5;
    const Zmm& log2 = zmm6;
    const Zmm& log2_e = zmm7;
    const Zmm expCoeff[] = { zmm8, zmm9, zmm10, zmm11, zmm12 };
    mov(eax, 127);
    vpbroadcastd(i127, eax);
    vpbroadcastd(expMin, ptr[rip + expDataL + (int)offsetof(ConstVar, expMin)]);
        ...

各種定数をレジスタにセットします。 vpbroadcastdはfloat変数1個を32個Zmmレジスタにコピーする命令です。

    vpbroadcastd(expMin, ptr[rip + expDataL + (int)offsetof(ConstVar, expMin)]);

はXbyak特有の書き方です。 LabelクラスexpDataLは各種定数(ConstVarクラス)が置かれている先頭アドレスを指します。 ripで相対アドレスを利用し、Cのoffsetofマクロでクラスメンバのオフセット値を加算します。

"rip相対アドレス"
rip相対アクセス

メインループ

vminps(zm0, expMax); // x = min(x, expMax)
vmaxps(zm0, expMin); // x = max(x, expMin)
vmulps(zm0, log2_e); // x *= log_2(e)
vcvtps2dq(zm1, zm0); // zm1 = n = round(zm0)
vcvtdq2ps(zm2, zm1); // zm2 = float(zm1)
vsubps(zm0, zm2); // a = x - n
vmulps(zm0, log2);   // a *= log2

アルゴリズムの1から5行目に対応します。 vminps, vmaxpsで入力値を[expMin, expMax]の範囲内にクリッピングします。 vmulpsでlog_2(e)倍しvcvtps2dqで整数へ最近似丸め(round)します。 結果はint型になるのでそれをvcvtdq2psでfloat型に戻します。

vmovaps(zm2, expCoeff[4]); // 1/5!
vfmadd213ps(zm2, zm0, expCoeff[3]); // b * (1/5!) + 1/4!
vfmadd213ps(zm2, zm0, expCoeff[2]); // b(b/5! + 1/4!) + 1/3!
vfmadd213ps(zm2, zm0, expCoeff[1]); // b(b(b/5! + 1/4!) + 1/3!) + 1/2!
vfmadd213ps(zm2, zm0, expCoeff[0]); // b(b(b(b/5! + 1/4!) + 1/3!) + 1/2!) + 1
vfmadd213ps(zm2, zm0, expCoeff[0]); // b(b(b(b(b/5! + 1/4!) + 1/3!) + 1/2!) + 1) + 1

アルゴリズムの6行目に対応します。 vfmadd213ps(x, y, z)は積和演算命令で、x = x * y + zを実行します。

// zm1 = n
vpaddd(zm1, zm1, i127);
vpslld(zm1, zm1, 23); // 2^n

アルゴリズムの7行目に対応します。 ここはちょっとわかりにくいので次節で解説します。

vmulps(zm0, zm2, zm1);

アルゴリズムの8行目に対応します。 これでexp(x)の計算が終了です。

floatのフォーマット

アルゴリズムの7行目を実装するためにfloatのフォーマットの説明をします。 floatは符号ビットs、指数部e、仮数部fからなります。 それぞれsが1ビット、eが8ビット、fが23ビットで合計32ビットです。

役割符号指数部仮数部
記号sef
ビット数1823

ビットパターンが[s:e:f]で表されるfloatは(-1)^s × 2^(e-127) × (1 + f/2^23)という値を表します。

たとえばx = 0なら0 = (-1)0× 20× (1 + 0)とかけるので 符号は0(0または正)、指数部はe = 127、仮数部f = 0です。 逆にs = 1, e = 130, f = 0x123456で表されるビットパターン[s:e:f]=0xc1123456はfloatとして- 2130-127× (1+f/223) = -9.137です。

前節では整数nに対してfloat(2n)が欲しかったのでした。 これに対応するビットパターンはs = 0, e = n + 127, f = 0です。 つまり((n + 127) << 23)という32ビット整数がfloatの2nを表すのです。

// zm1 = n
vpaddd(zm1, zm1, i127);
vpslld(zm1, zm1, 23); // 2^n

したがってvpadddでintの127を足し、vpslldで左23ビットシフトすることで必要な値を得られます。

floatからintへの変換

floatをintに丸める方法はいくつかあります。 今回はSSEの時代からある変換命令vcvtps2dqを使いました。 これは丸め方法がグローバルな設定に依存します。 通常モードを変更することはありませんが、もし別の設定を使うことがあるならこの方法は使えません。

intへの切り捨て専用命令vcvttps2dqというのもあります。この場合は0.5を足してから切り捨てれば四捨五入となります。 しかし負の場合は0.5を引く必要があり、やや複雑になります。

次にvroundpsという丸めモードを設定して使える命令があります。 しかしこの命令はAVX2までで何故かAVX-512用に拡張されていません。

代わりに追加されたvrndscalepsは丸めモードを自分で設定できます。 結果はfloat型になるのでintにするにはvcvtps2dqが必要です。 今回のアルゴリズムは整数にした後、floatとintの両方の型の値が必要だったのでレイテンシの短いvcvtps2dqを利用しました。

端数処理

floatを16個ずつ処理すると元の配列の個数nが16の倍数でないとき端数が出ます。 その処理方法について解説します。

AVX2までのSIMD命令では端数処理が苦手でした。 命令が16単位なので残り5個をレジスタに読み込むといった処理がやりにくいのです。 そのためSIMD命令を使わない通常の方法でループを回す方法をとることが多いです。

AVX-512ではそれを解決するためのマスクレジスタk1, ..., k7が登場しています。 マスクレジスタは各ビットがデータ処理する(1)かしない(0)かを指定するレジスタです。 データ処理しない場合は更にゼロで埋める(T_z)か値を変更しないかを選択できます。

たとえば

vmovups(zmm0|k1|T_z, ptr[src]); // zmm0 = *src;

でk1 = 0b11111;の場合、下位5ビットが立っているのでfloat *srcのsrc[0], ..., src[4]だけがzmm0にコピーされ、T_zを指定しているので残りはゼロで埋められます。

実装コードでの解説に戻ります。

and_(ecx, 15); // ecx = n % 16
mov(eax, 1); // eax = 1
shl(eax, cl);  // eax = 1 << n
sub(eax, 1);   // eax = (1 << n) - 1 ; nビットのmask
kmovd(k1, eax); // マスク設定

ecxにループの回数nが入っているとき15とandをとり端数を得ます。 ((1 << n) - 1)はデータが入っていない部分が0となるマスクです(n = 3なら0b111となる)。

vmovups(zm0|k1|T_z, ptr[src]);

vmovups(zm0, ptr[src])はsrcからzm0に16個のfloatを読む命令ですが、 vmovups(zm0|k1|T_z, ptr[src])とk1でマスクすると指定したビットが立った部分しかメモリにアクセスしません。

ここで重要な点は内部的に512ビット読み込んでからゼロにするのではなく マスクされていない領域にread/write属性が無くても例外が発生しないという点です。

安心してページ境界にアクセスできます。

係数の決め方

最後にアルゴリズムの6行目

6. z = 1 + b(1 + b(1/2! + b(1/3! + b(1/4! + b/5!))))

の値の改善方法について紹介します。 この式は無限に続く和を途中で打ち切ったものでした。 したがって必ず正しい値よりも小さくなります。

1/k!の値を微調整することで誤差をより小さく出来ます。

bのとり得る範囲はL = log(2)/2として[-L, L]でした。 関数f(x) = 1 + A + Bx + Cx2 + Dx3 + Ex4 + Fx5として 区間[-L, L]でf(x)とexp(x)の差の2乗誤差の平均を最小化する(A, B, C, D, E, F)を見つけます。

数学的にはI(A, B, C, D, E, F):=∫_[-L,L](exp(x) - f(x))2 dxとして IをA, B, C, D, E, Fで偏微分した値が全て0になる解を求めます。

Mapleでは

f := x->A+B*x+C*x^2+D*x^3+E*x^4+F*x^5;
g:=int((f(x)-exp(x))^2,x=-L..L);
sols:=solve({diff(g,A)=0,diff(g,B)=0,diff(g,C)=0,diff(g,D)=0,diff(g,E)=0,diff(g,F)=0},{A,B,C,D,E,F});
Digits:=1000;
s:=eval(sols,L=log(2)/2);evalf(s,20);

で求めました。 雑な比較ですが単純に打ち切ったときに比べて誤差が半分程度になりました。

Sollyaを使って

remez(exp(x),5,[-log(2)/2,log(2)/2]);

で求めるやり方もあります(個人的には2乗誤差を小さくする前者の方がよい印象 : 数値計算専門の方教えてください)。

まとめ

exp(x)の近似計算の方法とAVX-512特有の命令の紹介をしました。 端数処理をうまくできるマスクレジスタは便利ですね。 昔に比べてSIMDレジスタの幅が大きくなっているのでテーブル引きをせずに計算した方が速くなることが多いようです。

Maneki & Neco ミートアップの資料を公開します

$
0
0

Neco プロジェクト責任者の ymmtです。

Neco プロジェクトはサイボウズのクラウドサービス基盤を Kubernetes をベースとして刷新するプロジェクトです。 3か年計画の3年目ですが、すでに国内3箇所でデータセンターを稼働しています。 Maneki プロジェクトは Neco の新データセンターに既存サービスを移行していくプロジェクトです。

Neco と Maneki のメンバー合同で2月18日にミートアップを開催する予定だったのですが、新型コロナウィルスの影響を避けるためやむなくキャンセルいたしました。 開催に備えて準備していた資料が多数ありますので、本記事で公開いたします。

cybozu.connpass.com

Neco プロジェクトの歩き方

Neco プロジェクトは多数のリポジトリやコンテナイメージから構成され、そのほとんどが公開されています。 こちらでは実際に動く Neco をデモしつつ、Neco の成果物をどう活用できるか紹介する予定でした。

speakerdeck.com

Maneki 流 Kubernetes 初学者の歩き方

Kubernetes は Linux コンテナ以外に、Pod/Service/Deployment/PersistentVolume といった独自の様々な概念を導入しています。 Maneki チームで Kubernetes を基本から学んでいった取り組みについて紹介する予定でした。

speakerdeck.com

CKEで始める!はじめてのKubernetesクラスタインプレースアップグレード

CKEはサイボウズが自社で開発している Kubernetes クラスタ構築・運用ソフトウェアです。 構築後のバージョンアップや Node の追加・更新まで完全に自動化しているのですが、中でも Kubernetes クラスタのアップグレード時に Pod を drain せずに無停止アップグレードできるのが最大の特徴となっています。

通常の Kubernetes クラスタアップグレード方式を紹介し、CKE がどうやって無停止アップグレードを実現しているか解説する予定でした。

speakerdeck.com

Necoのストレージ、TopoLVMで何を実現するのか

TopoLVMはサイボウズが開発している Kubernetes 用のストレージプラグインです。 LVM で動的にローカルストレージを切り出して利用することが可能です。

TopoLVM の仕組みと、サイボウズでの今後の活用見通しについて紹介する予定でした。

speakerdeck.com

OSSへの貢献ノウハウ〜LinuxカーネルからRookまで

Kubernetes とその周辺のソフトウェアは OSS で、Neco の成果物もほぼすべて OSS です。 業務上 OSS コミュニティとの接触は不可欠で、それをどのようにしているか紹介する予定でした。

本内容については先日別に記事を用意しましたので、そちらもご覧いただければと思います。

speakerdeck.com

MySQL の構成変更について

現行データセンターで稼働している MySQL を複数段階で構成を変更し、Neco のデータセンターに移行していく計画を紹介する予定でした。

speakerdeck.com

Necoを支えるチームビルディング

Kubernetes は 3 か月に一度新しいバージョンが出ます。サポート期間は直近 3 バージョンなので、古いバージョンを使い続けることはできません。 周辺のソフトウェアも同様にサポートする Kubernetes バージョンを更新していくため、プロダクション用途で使っている Kubernetes クラスタを維持可能な形で運用するのは一筋縄ではいきません。

Neco チームには Kubernetes の専門知識を持つ人材が 10 名規模で揃っていますが、このような人材の獲得・育成について紹介する予定でした。

speakerdeck.com

まとめの代わりに

ミートアップを楽しみにしていただいていた方々には申し訳ありませんでした。 いずれ事態が落ち着けばまた企画を再開できればと考えています。

今は、皆様のご無事を願います。それでは。

リモート・モブプログラミングという働き方

$
0
0

こんにちは!kintone開発チームの太田 (@kigh) です。 この記事では、自分のチームで2年以上続けているリモート・モブプログラミング(以下「リモート・モブ」)について、 進め方の具体例や所感、実際にやる上でのTipsを紹介したいと思います。

リモートワークが急速に普及する中、リモート・モブは働き方の選択肢の一つとして存在感を増してきていると思います。 この記事から少しでも参考になる点が見つかれば幸いです。

リモート・モブプログラミング

この記事では、テレビ会議システムなどのツールを使いつつ、物理的に離れたチームでモブプログラミングをすることをリモート・モブと呼びます。

現在、kintoneの新機能開発メンバーは6拠点のオフィスに分散し、また多くのメンバーがカジュアルに在宅勤務を活用するリモートチームとなっています。 また2018年から2年以上、全ての設計・実装タスクを原則モブプログラミングで行っています。 チームメンバーは同じ場所にいるわけではないので、よくイメージされるような一つのPCを囲んでのモブプログラミングはできません。 そのためテレビ会議システムを使い、画面共有機能も活用して開発を進めています。

kintoneチームに限らず、サイボウズでは少なくとも10以上のチームがリモート・モブで開発を進めています。

自分のチームでのやり方

リモート・モブの一例として、自分のチーム(東京2名・大阪2名の4人チーム)でのやり方を紹介したいと思います。

まずイメージしやすいよう、スクリーンショットで作業の様子を紹介します*1。 このように、IDEを画面共有しつつ、議論しながら実装を進めます。 ふりかえりなども同様に、議事録などを画面共有しながらやっています。

リモート・モブ作業中のスクリーンショット。IDEのウィンドウが大きく画面共有され、その脇に各メンバーの顔のカメラ映像が並んでいる
リモート・モブ作業中のスクリーンショット

より具体的な進め方は以下の通りです:

  • テレビ会議に接続してデイリースクラム開始→終わったらその流れでモブプロ開始
  • 基本は全ての作業をモブで進める
    • 適宜ドライバー(=テレビ会議で画面共有して、キーボードをタイプする人)を交代する
      • 場合によるが、最短7分ローテーションで
      • コードが中途半端でも気にせずコミット、後で整理する
    • ただし調査や定型的タスクなど、個人単位でやった方が効率が良いと判断した場合は、再開時間を決めて、一時的にモブを解散することも
  • ミーティング・勉強会などチーム外での仕事がある人は、自由に離脱・合流してよい
    • 1〜2人抜けても、残った人でモブは続行する
  • 疲れたら休憩を取る
  • チーム外の人と相談が必要になったら、随時テレビ会議に接続し、モブに加わってもらう
    • 常時モブをしているのは実装エンジニアだが、試験設計はテストエンジニアと、仕様やスコープ調整などはプロダクトオーナーと、文言検討はライターと一緒に、など
  • 16時になったらデイリーふりかえりをして、以降は自由時間
    • 個人で勉強したり、メンバーに質問したり、細々した仕事を片付けたりなど自由に
    • 新しい試みやチャレンジは個人単位で始めることも多いので、この自由時間も重要

なお、サイボウズ社内でもチームによって色んな文化があり、リモート・モブのやり方も異なっています。 また、同じチーム内でもやり方は随時変化しています。 そのため、ここで紹介したのはあくまで一例です。

リモート・モブに思うこと

2年以上リモート・モブをする中で、メリット・デメリットどちらもわかってきました。 今の所、私たちにとってはメリットが上回っており、「常に一箇所に集まれるわけではないチームにとって、もっとも良い働き方なのでは」と感じています。 ここではメリット・デメリットを中心に、リモート・モブに関する自分の所感をまとめたいと思います*2

良いところ

拠点間および在宅メンバーとの情報格差を最小化できる

これが今の所、一番良い点だと思っています。 ある程度の規模のプロダクトの開発をチームで進める上で情報格差は敵です。 悪いことに、リモートチームではどうしても、情報格差が生まれる確率は高まります(特に多数派の拠点・場所がある場合)。

例えば「最近決まった〇〇の設計について、他の人は知っているけど、自分は知らない...」という状況を想像してみてください。 直近の作業の遅れにつながることはもちろんですが、それ以上に、仕事に対するモチベーション低下、さらにはキャリアに対する不安にまで発展することもあるのではないでしょうか。

リモート・モブは、物理的な場所・属人化に伴うサイロという2つの壁を超えて情報格差を減らすことができる方法です。 自身の経験でも、個人作業からリモート・モブへ移行したことで、情報格差やそれに伴う不安が劇的に改善しました。

新メンバーの受け入れに強い

新しいメンバーにはチームへのオンボーディングをして、スムーズにジョインしてもらうことが重要ですが、 リモートチームの場合、その負荷が特定のメンバーに集中したり、あるいはうまく進められなかったりすることがあります。 リモート・モブなら、仮想的に場を共有していることが助けになります。

これは最近の実話なのですが、自分が勤務する大阪オフィスに新たに中途メンバーが入社してくれて、チームメンバーが自分1人から3人に増えました。 このような場合、もしリモート・モブ体制がなければ、新メンバーのガイダンスや教育のために、自分がしばらく休まず出社することが必要でした。 しかし今回、新メンバーには最低限のガイダンスをした後はモブに加わってもらうことで、新メンバーが周りの人に相談しやすい状態を自然と作ることができました。 結果、自分がやむなく休みを取った場合でも、何も問題は起きませんでした。

弱いところ

一方で、物理的に場所を共有していないために「ここは弱いな」という点もあります。

抽象的な議論がしづらい

プログラミングもそうですが、その前の要求の整理や設計などの段階で特に感じることです。 具体的には、「対面で話せばもっとすぐ伝わるのに!」とか「クラス図などをホワイトボードに手書きしたいが出来ない!」といったケースです。

対面よりも伝えづらい、というのはどこまでも付いて回る話ではありますが、議論する相手との信頼関係やコンテキスト共有を高めることで改善できる部分はあります。

また、ホワイトボードについてはツールでの改善が可能です。私たちの場合、電子ホワイトボードなどの導入にはまだ至っていませんが、プレゼンツールで描いた図やテキストエディタを画面共有することで案外なんとかなっています。

チームビルディングの速度が遅い

作業中は常にテレビ会議でつながっているとは言え、拠点が別だとランチや飲み会などはもちろん一緒に行けません。 また実際に顔を合わせて喋っていないため、心理的な距離感も縮まりづらい傾向はあると思います。

これには、意識的に雑談の時間を作ったり、出張して実際に会うことなどがとても効果的で、実際にそのような機会を作るよう心がけています。

過去の悩み

少し余談になりますが、コミュニケーションの円滑さという面で、やはり「テレビ会議は対面に敵わない」という実感は正直言ってあります。 そのため、「頑張ってリモートチームを作り上げなくても、拠点ごとに独立した体制を作った方が良いのでは・・・」と悩んだことが何度かありました。

しかし、昨今の在宅勤務の広まりから、リモートチームとして開発できることはほぼ必須になってきていると思います。 実際に最近、新型コロナウイルスの影響で、メンバーの大半が在宅勤務を選択していますが、プロダクト開発は混乱なく続けることができています。 こうした経験から、拠点が複数に分かれているかどうかによらず、リモートチーム前提で良いチーム体制を作ることは重要で、その一つの解がリモート・モブである、と自信を持って思えるようになりました。

リモート・モブTips集

記事の最後では、私たちがふりかえり等を経て辿り着いたTipsのうち、自分が気に入っているものをいくつか紹介しようと思います。

コミュニケーションの快適さ(ツール)にこだわる

通常のモブと比べたリモート・モブの弱点は、コミュニケーション帯域の狭さです。 経験のある方もおられるかもしれませんが、複数人でテレビ会議を使ってうまく議論するのは単純にスキルが必要で、ある程度時間をかけて上手になっていきます。 もしテレビ会議で会話がスムーズにできないと、ツールのせいでうまくいかないのか、自分たちのスキルのせいでうまくいかないのかが切り分けられません。 なので、ツールにこだわる価値はあると思っています。

実体験としては、あるツールを使ってリモート・モブをしていると、声を発してから相手に伝わるまでのラグが数秒に達することがありました。 その結果、発話の衝突がしばしば起きて、話がしづらい状況となりました。 細かい話のようですが、テレビ会議だと音声がコミュニケーションのほぼ全てなので、そこがギクシャクするのは結構厳しい状態です。 結果的に、ラグが短くなる別ツールに乗り換えて解消しました。当たり前品質大事。

カメラ表示をオンにする

メンバーがシャイでなければ、カメラを用意して顔を見えるようにすると良いです(もちろん見せない自由も尊重します)。 自分が話しているときに、頷きなどのリアクションが見えるか見えないかは、思ったよりも心理的な安心感が違います。 あとは、顔が見えるとより親近感も増しますね。

意識的に休憩する

普通のモブプロよりもリモート・モブの方が、休憩を忘れがちになりました。 他のメンバーが疲れていても気づきづらいからかもしれません。

休憩しないと視野が狭くなって、普段なら気づける解法にも気づけなかったりします(これは個人作業でも同じはず)。 肩も凝るし、強い気持ちで休憩を取るのが良いです。休憩タイミングをルールで決めるのも良いです。

抜けていた人が戻ってきても「一旦共有しますね〜」をやらない

ミーティングなどで離脱していた人が戻ってきたとき、親切心からその間の進捗を共有することがあります。 ですが、意外に共有しなくてもなんとかなることが多いです(少し作業を見ていたり、メモがあればそれを見るだけで十分キャッチアップできる)。 むしろ離脱・合流を気にせずモブの流れを維持する方が、離脱していた人も気を遣わないというか、快適に作業を進められました。 もちろん新メンバーがいる場合など、特定のメンバーが置いてけぼりになっていないか、質問しやすい雰囲気かなどは常に気をつけています。

「モブプロ宣言」を作ってみる

ちょっと変な標題ですが、要は「何のためにリモート・モブをやっているのか」を言語化してみる、ということです。

自分のチームでは、モブプロをしばらく続けていると、「ずっとモブプロやっていて良いのか?」といった意見がメンバーから湧き上がってきました。 これを議論するには、モブプロをやる目的がメンバー間で共有されている必要がありますが、最近までその言語化はしてきませんでした。

そこで、有志メンバーでワークショップをして、「モブプロ宣言」として言語化してみました。その成果物がこちらです:

【kintone開発チーム・モブプロ宣言】
私たちkintone開発チームは、 
 1. チームメンバー間での知識共有・学習の機会を増やす
 2. フロー効率を優先し優先順位の高いPBIから完了する
 3. 必要なときに様々な職能の人にすぐ議論に加わってもらう
 4. リモート・在宅開発体制でも問題なく開発を行う
 5. 会議・休暇・兼務などで抜ける人がいてもチームとしてスムーズに作業を進める
ことによって
 学習・品質・属人化防止・働きやすさ・楽しさ・安心感
というメリットが得られるため、モブプログラミングを実践している。

一度言語化してみることで、自分たちの仕事のやり方への納得感が増した感じがしますし、気づいていなかったメリットに気づくこともできました (例えば自分は「楽しさ」という軸は意識していませんでしたが、言われてみれば確かに、と思わされました)。 逆に、ここに当てはまらないようなタスクであればモブでやらなくても良いよね、という話もできるようになりました。

おわりに

私たちは、仕事時間の多くをリモート・モブで回しています。 そのためリモート・モブは、個々のタスクの結果だけでなく、モチベーションや個人のキャリアにまで、幅広い影響があると感じています。 少し大げさに言えば、リモート・モブは一つのプラクティスである以上に、働き方そのものに思えます。 このやり方がずっとベストかもちろんわからず、常に考え続ける必要はありますが、現時点では多くのメリットがある働き方だと思い実践しています。

この記事が何かの参考になれば幸いです。それでは、Happy remote mob-programming!!

*1:ちょうど、この記事を執筆している2月27日付で、一部拠点は原則在宅勤務とする弊社プレスリリースが出ました。このスクリーンショットは、その社内向け通達が出る数時間前に撮影したものです。

*2:ここでは「リモート」と「モブ」の両方を前提とした事項に絞って書きます。単にモブと個人作業とを比較したメリット・デメリットなども含めると、長くなりすぎてしまうためです。

OSSへの貢献ノウハウ: ユーザサポート編

$
0
0

はじめに

こんにちは、Necoプロジェクトsatです。本記事は先日公開した以下の記事の続編です。

blog.cybozu.io

上記の記事ではOSSプロジェクト全体を盛り上げる手段を次のように紹介しました。

プロジェクト全体を盛り上げるには例えば次のような方法があります。

  • 自社に直接関係が無いissue/PR発行、レビュー
  • ユーザサポート
  • イベントでの発表

本記事では、このうちのOSSにおけるユーザサポートとはどのようなものかについて、Rookにおいて発生した実際の問題を例として書きます。この問題の根本原因はカーネルだったためにカーネルの用語がいくつか出てきますが、あまり気にせずにフィーリングで読んでいただければとおもいます。本記事で一番伝えたいのはカーネルの技術的な知識ではなくOSSにおけるユーザサポートのノウハウです。

Rook、およびRookが管理するCephについては過去記事をごらんください。

blog.cybozu.io

blog.cybozu.io

きっかけ

筆者がKubeCon North America 2019に参加していたときに、Rookのgithubにおいて気になるissueを見つけました。

github.com

まずはこのissueがどういう問題かについて書きます。前提知識として、Rookが管理するCephクラスタは、多くのノード上にCeph用のサービスを動かしています。このうちのOSDというサービスが動いているノードが、特定の条件を満たすとCPU使用率が100%になり、かつ、その後にノード全体が無反応になるというのがこの問題です。

業務システムでこのような問題が発生した場合は大きな問題になりますし、かつ、同じ問題に遭遇した人は数人いたため、極めてレアなケースというわけでもないことがわかりました。

さらにissueを読み進めると、問題発生時には次のコメント内の画像のようなLinuxカーネル(以下カーネルと記載)のログが出力されることがわかりました。

github.com

ここで重要なのは次のメッセージです。

... task XX blocked for more than 120 seconds.

筆者はかつてカーネルの開発をしていたこともあり、このメッセージはユーザプロセスではなくカーネルの問題が起きた場合に出ることを知っていました。もう少し詳しく言うと、カーネル内の排他制御の問題で、特定のプロセスが長時間、あるいは永久に動けない状況になっていたことを示しています。

しかしながら上記のコメントの後の流れを見たところ、Cephを含めたユーザプロセスの問題について色々と議論しているものの、カーネルの話はまったく出てきていませんでした。このことから、このissueの関係者にはカーネルに詳しい人がいないのだろうということがわかりました。

ここまでわかった段階で、以下のような理由によって筆者はこのissueの解決に協力することを決めました。

  • Rookを使っているからにはプロジェクトに貢献して盛り上げたい*1
  • 自分の知見を活かせる、かつ、自分が協力しないと恐らく先に進まない
  • 現在Necoではこの問題に遭遇していないが、いずれ遭遇するかもしれない。その場合の影響が大きい

どのような貢献をするか

問題の解決に協力するとして、具体的にどのようなことをするかも考えなければいけません。このようなときに陥りがちな罠は、とにかく何でもかんでも貢献しようと勇み足になって全てを自分でやろうとしてしまうあまり、本業をおろそかにしてしまうことです。

筆者はこの罠に陥らないように、自分のやることを以下のように定めました。

  • 問題に遭遇した人から必要な情報を収集する
  • 上記の情報を使ってカーネル開発者とやりとりする
  • 得られた情報をissueにフィードバックする
  • 問題を回避できればそこまでで終了とする
  • カーネル修正が必要になった場合は積極的には手を出さない、あるいは自分の環境で遭遇するまでは低優先度で取り組む

企業の製品サポートでいえば製品内の不具合の直接調査する役割ではなく、関係者を巻き込んで問題解決を推進する取りまとめ役といえるでしょう。

調査の流れ

情報収集

問題がカーネルにあるとわかったものの、カーネルの中のどこに問題がありそうかを明らかにするために、もうすこし深掘りをすることにしました。具合的には、次のコメントにおいて、問題はカーネルにあることを述べた上で、問題に遭遇した人々に情報収集を依頼しました。

github.com

各情報の採取意図については本記事の範囲を超えますので、必要であれば上記コメントを読んでください。

幸いにも上記の依頼に対していくつかの反応がありました。そのうち最も有用だったのは以下のコメントです。

github.com

このコメントによって、問題発生時のカーネルのバックトレースの情報が得られました。

[51717.039319] INFO: task kworker/2:1:5938 blocked for more than 120 seconds.
[51717.039361]       Not tainted 4.15.0-72-generic #81-Ubuntu
[51717.039388] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[51717.039426] kworker/2:1     D    0  5938      2 0x80000000
[51717.039471] Workqueue: xfs-sync/rbd0 xfs_log_worker [xfs]
[51717.039472] Call Trace:
[51717.039478]  __schedule+0x24e/0x880
[51717.039504]  ? xlog_sync+0x2d5/0x3c0 [xfs]
[51717.039506]  schedule+0x2c/0x80
[51717.039530]  _xfs_log_force_lsn+0x20e/0x350 [xfs]
[51717.039533]  ? wake_up_q+0x80/0x80
[51717.039556]  __xfs_trans_commit+0x20b/0x280 [xfs]
[51717.039577]  xfs_trans_commit+0x10/0x20 [xfs]
[51717.039600]  xfs_sync_sb+0x6d/0x80 [xfs]
[51717.039623]  xfs_log_worker+0xe7/0x100 [xfs]
[51717.039626]  process_one_work+0x1de/0x420
[51717.039627]  worker_thread+0x32/0x410
[51717.039628]  kthread+0x121/0x140
[51717.039630]  ? process_one_work+0x420/0x420
[51717.039631]  ? kthread_create_worker_on_cpu+0x70/0x70
[51717.039633]  ret_from_fork+0x35/0x40

どうやらXFSファイルシステムが関係していそうだということがわかりました。さらに、この問題はこれまでにXFSでしか発生実績がなかったことより、筆者はXFSが有力な容疑者の一人だとみなしました。

カーネル開発者への相談

ここからやるべきことは既存障害調査、およびXFS開発者への相談です。前者は自分だけでもできるので、まずはこちらからやりました。具体的には次のようなことをしました。

  • XFS開発メーリングリストの過去ログを上記トレース内の特徴的な単語を使って検索した
  • カーネルのコミットログを上記と同様に検索した

しかし、この問題に関係ありそうな議論、あるいは修正は見つかりませんでした。

github.com

続いてXFS開発メーリングリストでXFSの開発者達に相談してみることにしました。

ここで問題になるのが、報告するときに提供すれば好ましい情報がいくつか足りていないことでした。たとえば再現手順や最新カーネルにおける再現確認などです。ただし、次の事情によって、これらの情報を得るのは短期的には難しいと判断しました。

  • OSSのissueにおける情報収集依頼は商用製品のサポートに比べて要求した情報が得られる確率が低い。報告した後に無反応になる人も多い
  • 誰も再現方法を見つけられていない。ゆえに筆者の環境において最新カーネルでの再現も難しい

この手の情報が足りないと報告そのものが無視されがちなのですが、やむをえず、できる限りの情報を添えて、XFS開発メーリングリストに相談しました。幸いにもこの後すぐにXFSの元メンテナであるDave ChinnerさんがCephによってブロックデバイスを提供するkrbdドライバが怪しいということを教えてくれました。

この後はすぐにDaveさんのコメントを反映してCephのカーネル部分の開発者メーリングリストに相談しました。幸いにも、こちらの相談もCephカーネル機能のメンテナの一人であるIlya Dryomovさんからすぐに回答がありました。彼にはこの問題がkrbdとXFSの相互作用によって発生すること、既知のものであること、修正見込みであること、および回避策があることを教えていただきました

結果の報告

この結果をもとに、筆者はもとのRookのissueに、根本原因、回避方法、修正見込みについてコメントをしました。

github.com

このようなときに注意すべきなのはCeph開発メーリングリストで教えてもらったカーネルの言葉をそのまま使わずに、Rookユーザにわかるように書くことです。これはサポート技術の基本ともいえます。

ここまで書いたところでRookのメンテナの一人であるSébastien Hanさんから提案を受けて、Rookのよくある問題を掲載するドキュメントにこの件について書いたところで、このissueはcloseとなりました。

github.com

おわりに

本記事ではみなさまに次のようなことをお伝えしました。

  • OSSのサポートとは具体的にどのようなものか
  • 一般的なサポート技術、および、OSSのサポート技術

みなさんもお使いのOSSにおいて何かしら貢献できそうなことがあり、かつ、リソースに若干余裕のあるかたは、やってみて、OSSを盛り上げてみてはいかがでしょうか。きっとよい経験になると思いますよ。

*1:サイボウズにはOSSに積極的に貢献するよう努めるというポリシーがあります

Folding@homeを通じてCOVID-19治療薬の発見に貢献します

$
0
0

こんにちは。Neco@dulltzです。

サイボウズでは、オンプレミス機材の一部を活用してFolding@homeによるCOVID-19のタンパク質構造予測に貢献することにしました。

ご自身で動かしてみたいというかた向けに、我々が使っているKubernetesマニフェストも記載しているのでぜひご覧ください。

Folding@homeとは

みなさんはFolding@homeを知っていますか?

Folding@homeとは、タンパク質構造予測に必要な計算処理を、世界中の有志による分散コンピューティングで推進するプロジェクトです。 10年以上昔の話になりますが、PlayStation 3を活用したプロジェクトを覚えている方も多いかと思います。

最近Folding@homeのブログにて、COVID-19の治療薬開発に使われるタンパク質構造予測を行っているという旨の記事が公開されました。

これを受け複数の企業がFolding@homeへの貢献、あるいは貢献の呼びかけを表明しています。

サイボウズもFolding@homeに参加中です

サイボウズもオンプレミス機材の一部を利用し、Folding@homeに参加しています。

2020年3月16日現在、24CPUコアのサーバ105台でFolding@homeのクライアントを実行しています。

デプロイ方法

今回の用途に使える機材はKubernetesノードであったため、Folding@homeのクライアントはPodとして実行することにしました。

KubernetesマニフェストやDockerイメージは https://github.com/richstokes/k8s-fahを参考にし、 それを元にいくつかの変更を加えています。

  • クラスタサイズに合わせてPod数を変えたいため、DeploymentではなくDaemonSetを使用する。
    • ただし、他のPodがスケジュールされるとPreemptされるようにPriorityClassresources.requestsを調整する。
  • PodSecurityPolicyによってread-onlyなrootファイルシステムの使用を強制しているので、Folding@homeのクライアントが書き込むパスにemptyDirをマウントする。
  • PodSecurityoPolicyによってrootユーザによる実行を禁止しているので、実行ユーザを変更する。
  • 万が一クラスタ内で不要な通信が行われるリスクに備えて、CalicoのNetworkPolicyでFolding@homeのPodがアクセス可能な範囲を制限する。
  • resources.limitsに指定したコアはすべて使って構わないので、 --power=fullオプションを指定する。

具体的なマニフェストは以下に記載しています。

github.com

まとめ

サイボウズのFolding@homeへの貢献と、Folding@homeクライアント実行方法について説明しました。
Kubernetesノードがあればすぐに参加することができるので、もし余ったノードがある方はぜひ参加してみてください。

COVID-19にまつわる状況が一刻も早く改善することを願っています。

Claraチームの開発・テストプロセスについて

$
0
0

こんにちは、ClaraチームでQAをやっている @ayuay46です!

この記事では、Claraの概要と、Claraチームでの開発・テストプロセスについてご紹介します。

Claraの誕生秘話

Claraとは、AWS版Kintoneが使用する米国向け販売管理システムのコードネームです。

AWS版Kintoneのリリースまでは、日米ともにKintoneは自社クラウド cybozu.com を利用しており、販売管理システムも日本で作った共通のものを利用していました。

そのため、米国のマーケティングチームが現地に特化した新しい施策や売り方をしたい場合に、都度日本の開発チームに仕様変更・機能追加を依頼する必要がありました。

また、開発チーム側も日本向けと異なるロジックを実装する必要がありコストがかかっていました。

そのような問題を解決するために、米国向けのKintoneがAWSに移行するのに合わせて、販売管理のグローバルSaaSを利用した米国独自のシステムを構築しました。 国内データセンターからだとレイテンシが大きいため、AWSを利用しているYakumoを基盤としています。

Yakumoの詳細については他記事を御覧ください。

blog.cybozu.io

Claraの開発プロセスについて

Claraチームではアジャイル開発をしています。

f:id:cybozuinsideout:20200317143605p:plain
Clara開発プロセス

1週間スプリントで、要件ごとに上記の図のようなイメージで開発しています。 ※「相互レビュー」については後述。

要件は、販売管理システムを実際に利用する米国の現地メンバーと検討していくのですが、この段階からQAも入っています。 最もよく使われる操作方法はどういったものか、どういった手順をテストに落とすべきか、この段階から知ることができます。

また、リリース作業は、各要件のテストの進捗を把握しているQAが行っています。 回帰テストは自動化されているため、高速にリリースすることができます。

テストポリシー

Claraは外部サービスと多く通信するのですが、外部サービスの挙動についてはテスト対象としていません。

テストの範囲を明確に「自社製品」に限定することで、外部サービスを過剰にテストしないようにし、リリース速度の低下を防いでいます。

もちろん、手動でテストを実施するときは外部サービスを触るので、そこで外部サービスの仕様変更やバグに気づくことはあります。

また、テストはなるべく自動化しています。 テストピラミッドの考え方を参考にして、Large/Medium/Smallに分割しています。

Largeテスト

各サービスが結合された状態の挙動、かつ、ユーザーが最も利用すると想定しているケースをLargeテストとして実装しています。

Largeテストの範囲は「ハッピーパス」と呼ばれる正常系の代表ケースが主です。 他の細かなパターンはMediumテストやSmallテストでカバーしています。

f:id:cybozuinsideout:20200317143649p:plain
Largeテスト

Mediumテスト

APIテストがこれに当たります。Largeテストとは異なり、Mediumテストでは外部サービスと通信する箇所はモックを作成しテストしています。

f:id:cybozuinsideout:20200317143713p:plain
Mediumテスト

Smallテスト

いわゆるユニットテストです。 1つのクラスやメソッドなどのロジックのテストをします。 またフロントエンドのテストもSmallテストとして分類しています。

f:id:cybozuinsideout:20200317143751p:plain
Smallテスト

相互レビュー

ClaraではDevやQAという立場に関係なく、チームとして製品の品質を高める活動をしています。 活動の一つの例として、DevとQAがお互いの成果物をレビューします。

QAが作成したテストケースをDevがレビューし、過不足がないかを確認します。 基本的にはテストは自動化するのですが、自動化が難しい箇所などがあればQAが手動でテストを実施します。

また、テスト設計をもとに、QAはDevが実装したテストコードに過不足がないかを確認します。 不足しているテストは追加で実装します。 その場でDevとQAでモブプログラミングするか、または、Devが代表的な1ケースのみを実装しそれを参考にしてQAが実装します。

まとめ

Claraチームでは、DevとQAの境目が少しずつなくなってきたなと実感しています。

一方で、製品コードを見る機会も増えたため、テストがホワイトボックスになりすぎないよう気をつけていきたいと考えています。

テストプロセス改善も随時進めて行く予定です。 テストピラミッドの考えを参考にしてテスト自動化の方針を決めていましたが、開発が進みテストが充実してきた今、テストポリシーに沿えているのか振り返ったり、テストポリシー自体を見直したりもする予定です。


オンラインイベント開催ノウハウの共有

$
0
0

はじめに

こんにちは、Necoプロジェクトsatと申します。

サイボウズは先日Japan Rook Meetup #2というイベント(以下、本イベントと記載)を複数企業合同で、かつ、オンラインで開催いたしました。オンラインイベントの開催が盛り上がっている昨今、本イベントの開催によって得た知見を読者のみなさまに共有しようというのが本記事の目的です。

使用したツールと配信形式

本イベントはオフラインイベントでよくある形式の、次のようなものでした。

  • 20分のセッション(質疑あり)が3本
  • 10分のLT(質疑なし)が1本

このようなイベントを以下のようなサービスを使って開催しました。

  • twitter: 普段の運営メンバ(発表者含む)の連絡のため。ダイレクトメッセージ機能を使う。
  • connpass: 勉強会支援サービス。開催日時やコンテンツなどの告知に使う。
  • zoom: ビデオ会議サービス。チャットも可。司会や発表者が喋って、その様子を録画するために使う。
  • youtube live: 動画配信サービス。zoomによって録画した動画を一般参加者が見るために使う

配信時の様子を簡単にあらわした図を以下に掲載しておきます。

f:id:cybozuinsideout:20200401162220j:plain

オンラインイベント開催に使用できるツールの組み合わせはいくらでも考えられますが、本記事では我々が実際に使った上記組み合わせを前提として話を続けます。これらを選んだ理由は、過去にこの組み合わせでオンラインイベント開催をした実績を持っている社員がいたことです。

個々のツールの使い方については本記事の対象外とします。

ノウハウ集

本節ではいよいよノウハウを紹介いたします。チェックリストのように使えるほうがみなさまが利用しやすいと考えたため、普通の文書を書き連ねるのではなく、列挙形式で紹介いたします。

  • 前節において述べたように関係者が喋るツールと参加者にその内容を配信するツールを分ける。参加者全員が互いにzoomの使い方を熟知して、かつ、見知った仲であれば全員zoomでもいいかもしれない。しかし、そうでない場合は予期せぬトラブル*1を避けたいため、発言できる人の数を絞っておきたい。
  • 質疑にはtwitterを使う。参加者には特定ハッシュタグ(本イベントの場合は#japanrook)で質問をつぶやいてもらって、運営がそれを拾って発表者が回答する。このとき次のことに注意する。
    • 質問チャネルが複数あるのが避けたいので1つだけに絞る。本イベントの場合はtwitterに統一した
    • twitterの画面を直接配信するのは避ける。理由は、かつて同じようなことをしたときに悪意をもって不適切な書き込みをした例を見たことがあるため。そうなった場合は後日録画したものを配信するのが面倒、あるいは不可能になる。
    • とりあげる質問はなるべく意味が複数にとれない答えやすいものにする。理由は参加者と「質問の意図は合っていましたか」のようなやりとりをするのが困難なため。なぜかというと、zoomで喋った内容がyoutube liveで配信されるまでには長いラグがあるため。たとえば本イベントの場合は30秒程度遅延が発生した。
  • 開始時刻にいきなりはじめるのではなく、数十分程度前からくらい「XX時から始まります」ということがわかる一枚スライドを出しておく。休憩時にも休憩中であることがわかるスライドを出す
  • 関係者が喋る際に顔を出すかは個々人に任せる。youtube liveに後々まで記録が残ることは周知する
  • 喋っていない人はミュートする、かつ、顔を出さないようにする。聴衆の気が散るのを避けるのが目的
  • 関係者間の業務連絡にはzoom chatを利用する。twitter上の質問をひろって記録しておくのにも便利
  • イベントは基本的には次の流れにする
    1. 司会の画面を共有して喋る。最後にスピーカーに振る
    2. スピーカーが画面を共有して喋る
    3. 喋り終わったらスピーカーの画面を共有したまま質疑応答
    4. スピーカーに画面の制御を戻して1に戻る
  • すくなくとも前日までには全員が流れを確認するリハーサルをするとよい。リハーサルは一度に全員でやる必要は無い。たとえば本イベントの場合は3回に分けた。リハーサルをする理由は次の通り。
    • 関係者が当日zoomの操作に手間取ることによる時間の浪費、最悪の場合は発表の断念を避けるため。たとえばzoomはOSによってはセキュリティ機能により画面共有に設定が必要なことがある。
    • オンラインイベントと異なり聴衆の反応が一切わからないという状態を体感して、慣れるため*2
  • 当日も運営やスピーカーは開催時刻の少し前に集まって接続確認などをするほうがよい。ちょうどオフラインイベントでスクリーンの表示確認をするようなもの

これらが唯一絶対の正解というつもりはありませんが、それなりにうまく回ったイベントのノウハウのひとつと考えていただければいいかと思います。

実際の配信結果

配信の模様は録画した上で公開していますので、ぜひごらんください。完璧に、とはいきませんでしたが、それなりに形にはなったかと思っています。

youtu.be

おわりに

本記事が「こんな状況でもなんとかイベントをしたい」という思いを持ったかたがたに役立つことを願っています。それに加えて、人と人との直接のやりとりが難しい中で、なるべく技術者同士で知見を共有する機会が増えることを願っています。

最後になりますが、本イベントの開催に多大なるご協力をいただいた運営メンバー、および発表者のかたがたに感謝の言葉を述べて、本記事を終わりにしたいと思います。

*1:たとえばミュートにしていない人がいて音がおかしくなる、など

*2:個人的には聴衆の反応を見て適宜アドリブを入れるやりかたを好むため、相当やりにくかった

フロントエンドの開発体験向上と脱レガシー

$
0
0

こんにちは。フロントエンドエキスパートチームの@nakajmgです。

私が所属しているフロントエンドエキスパートチームは、プロダクトのフロントエンドを横断的に支援するチームです。今回はフロントエンドエキスパートチームが行っている、プロダクトへの支援活動について紹介します。

フロントエンドエキスパートチームがどういったチームかに関しては、次の記事をご覧ください。

サイボウズのフロントエンドエキスパートチームの紹介

フロントエンドエキスパートチームの活動

サイボウズは主力プロダクトとしてGaroonkintoneを提供しています。この 2 つのプロダクトはそれぞれ提供開始の時期が 2002 年と 2011 年となっており、浅くない歴史を持っています。

サイボウズの Web フロントエンドは、フロントエンド専任ではないエンジニアがバックエンドと合わせて担当しています。そうした背景もあり、フロントエンドをモダンなスタックへ移行する対応などは積極的に行われていないという状況があります。

フロントエンドエキスパートチームは発足して 2 年ほどのチームですが、このような状況でチームが行ってきた、または行っている活動はおもに次のようなものです。

  • プロダクトの問題を解決する
    • kintone で使われていた Compassの依存解消
    • Garoon への Sass 導入
  • 社外の開発体験を向上させる
    • kintone カスタマイズやプラグインの開発に使う SDK の改善
  • 社内の開発体験を向上させる
    • Closure Tools から React + TypeScript への置き換え
  • 最新技術の調査と共有
    • ブラウザの最新動向チェック
    • プロダクトチームを交えたモブプロ

ここで挙げた以外にも、Web サイトの改善、社内勉強会やFrontend Weekly ランチといったフロントエンドの情報を社内に共有する会などを行っています。

プロダクトが抱えているレガシーに向き合う

脱 Compass や Sass の導入といった項目だけをみると、2020 年にもなった今になってそんなことをやっているの?と思われるかもしれません。

しかしながら、プロダクトが抱えている技術的な負債の返却は一朝一夕で行えるものではありません。前述したとおり、kintone も Garoon も歴史を積み重ねており、コードベースが巨大なものになっています。いかにデグレを起こすことなく、問題を解決して開発体験の向上が図れるか、そういった観点から慎重に対応する必要がありました。

段階的な脱レガシー

脱 Compass と Sass の導入にしても、もっと最新のイケてる技術で置き換えればいいのではと思いがちですが、動いているコードは様々な積み重ねでできていることを忘れてはいけません。

脱レガシーを目的として行う置き換えやリファクタリングは、エンジニアの開発体験を向上させるとともに、ユーザーへ価値を届ける速度や品質が落ちないようにするためのものです。これによって不具合を出してしまっては本末転倒になります。

そういった中で取る対応は、現実的なコストで、いかに既存の機能に影響を与えることなく、少しでも多くの負債を返済することだと考えています。

kintone や Garoon の場合はそれが脱 Compass や Sass の導入といったものでした。小さな一歩のように見えますが、プロダクトに関わるメンバーにとっては決して小さくないです。

大規模なレガシーに向き合うためには、腰を据えて段階的に改善をすすめていくのが最善手であると考えています。

開発体験の向上支援

脱レガシーへの対応と合わせて、開発体験の向上支援もフロントエンドエキスパートチームの重要な活動の一つです。この活動は社内に限らず、社外に向けた活動もあります。

サイボウズが提供している kintone は、HTML や JS や CSS を使ってユーザーが見た目をカスタマイズしたり、プラグインを作って機能を拡張できる仕組みがあります。カスタマイズやプラグインの開発を行う企業や個人の方が増えており、kintone のエコシステムは広がりを見せています。

フロントエンドエキスパートチームでは、このエコシステムの拡大を目指すとともに、カスタマイズやプラグインを開発するユーザーの体験を向上するための取り組みを行っています。

関連ツールやエディター連携、メタデータの管理やテストとデプロイなど、開発する上でのライフサイクル全般でベストプラクティスが実現できるような環境を作りたいという思いで取り組んでいます。

具体的な活動内容としては kintone のREST API Clientの開発を行っており、この活動は GitHub 上でオープンにロードマップを公開しながら進めています。

プロダクトに関わるユーザーの体験向上も、プロダクトの改善と同じように重要だと考えています。

次の段階

フロントエンドエキスパートチームは、前述した以外にもいろいろな活動をしています。

その中でも、現在は kintone で使っている Closure Toolsを React + TypeScript へと置き換える取り組みに力を入れて進めています。

この React 化の取り組みも一度にすべてを置き換えるような計画はしていません。段階的に部分部分を置き換えていって、徐々に React で書かれている部分を増やし、最終的にコードベースのほとんどが React で書かれている状況を目指しています。

既存コードと向き合いながら少しずつ前進しており、確かな手応えを感じています。

React 化の取り組みは現在フロントエンドエキスパートチームが主導していますが、これは近いうちにプロダクトチームが主導する形へと引き継がれる予定です。

フロントエンドエキスパートチームは特定のプロダクトに属するチームではないので、全てをフロントエンドエキスパートチームが行ってしまうと、プロダクトチームにとって別の形で負債となってしまいかねません。

プロダクトチームが主導できるように、フロントエンドに関する技術の共有や、勉強会などを行って支援しています。

おわりに

今回はフロントエンドエキスパートチームの活動について紹介しました。
大規模フロントエンドの脱レガシーは根気がいるものですが、チームとして焦らず着実に向き合っていきたいと思います。

そんなフロントエンドエキスパートチームでは、Web フロントエンド技術が好きな方の応募をお待ちしております。脱レガシーをはじめとした開発体験の向上に一緒に取り組みましょう。

サイボウズでは働く場所に縛りはありません。東京、大阪、広島、福岡、そのほかどんな場所からでも歓迎しています。

詳しい採用情報はこちら

「第 9 期サイボウズ・ラボユース成果発表会」開催

$
0
0

みなさんこんにちは。サイボウズ・ラボの内田です。

2020 年 3 月 30 日に第 9 期サイボウズ・ラボユース成果発表会を開催しましたので,その模様を紹介します。 今年は新型コロナウイルスの影響で,Zoom でオンライン開催されました。

サイボウズ・ラボユース

サイボウズ・ラボユースとは日本の若手エンジニアを発掘し,育成する場を提供する制度です。

ラボユース生が作りたいものをサイボウズ・ラボの社員がメンターとしてサポートし,開発機材や開発活動に応じた補助金,旅費の援助をします。開発物をオープンソースとして公開するという条件の元で著作権は開発者本人に帰属します。

今期はラボユース生 8 名が成果を発表しました。

発表会のレポート

発表会の集合写真です。(個人的に初めて Zoom で集合写真を撮りました。各人の顔が良く見えて結構いいですね。)

発表会の集合写真

サイボウズ・ラボユースの成果発表会としては初めてのオンライン開催でした。 去年までの発表会のような熱い雰囲気が作れるか心配でしたが, オンライン開催であってもラボユースらしい会になったと感じました。 ただ,恒例となっている発表会後の懇親会が開催できなかったのは残念でした…。

8 人の発表の様子を発表順に紹介します。

西川 哲也さん「手話単語分類」

drive.google.com

西川さんは 2019 年 5 月 17 日からラボユースで活動しています。メンターは中谷です。

手話と日本語は英語と日本語くらい違う言語だそうです。西川さんは手話を日本語に翻訳するシステムを作るための要素技術として,手話単語を分類(認識)するソフトウェアを作りました。1 つの手話単語を収録した動画を入力し,それが何の単語かを分類します。最終的にはスマホで手話をリアルタイムに翻訳するのが目標とのことで,スマホ使用に適する演算量や技術に限定して実装しました。

石巻 優さん「格子暗号を用いたHomomorphic Secret Sharingの実装」

docs.google.com

石巻さんは 2019 年 6 月 3 日からラボユースで活動しています。メンターは光成です。

石巻さんは,準同型暗号と秘密分散の手法を組み合わせた Homomorphic Secret Sharing を C++ で実装しました。

2 台以上のノードに情報を分散させることで各ノードに対して情報を秘匿したまま計算を行います。 加法秘密分散の手法では乗算時にノード間の通信が必要ですが,今回実装した手法は一部の乗算を通信無しで計算できます。

平田 遼さん「高速な楕円曲線の実装」

speakerdeck.com

平田さんは 2019 年 6 月 11 日からラボユースで活動しています。メンターは光成です。

平田さんは,楕円曲線暗号の計算で基本となる点 P のスカラ倍(k×P)を高速に計算するライブラリを作成しました。最も基礎となる足し算と 2 倍算は,sage-8.8 というライブラリと比べて,それぞれ約 14 倍,25 倍の高速化を実現しました。

江畑 拓哉さん「ニコニコ大百科と日本語の情報抽出方法の検討」

speakerdeck.com

江畑さんは 2019 年 6 月 17 日からラボユースで活動しています。メンターは中谷です。

江畑さんの最終目標はニコニコ大百科の知識を使った対話システムや情報検索システムを作ることだそうです。ラボユースでは,ニコニコ大百科の記事を前処理し,機械によって活用しやすい知識の形にすることを目指しました。例えば,記事中の各文において欠落した主語を,ニコニコ大百科のセクション構造を活用して補完する手法などを提案・実装しました。

和久井 拓さん「ミッキーシェイプの認識と検知」

和久井さんは 2019 年 6 月 27 日からラボユースで活動しています。メンターは中谷です。

和久井さんは現実世界で幸運のマークを探したいというモチベーションのもと,まずは画像内の幸運のマークを探すことにチャレンジしました。「幸運のマーク探し」は Kingdom Hearts 3 の世界に散りばめられている,3 つの円から構成される「幸運のマーク」を見つけて写真に収めるコレクション要素だそうです。今回作った幸運のマークを探すシステムを評価したところ,画像内に隠れた幸運のマークをいくつか見つけることができました。

和田 智優さん「クラウドシステムの非決定的性能バグ検査器」

和田さんは 2019 年 9 月 4 日からラボユースで活動しています。メンターは星野です。

和田さんは Java 製の分散並行処理ソフトウェアの「性能バグ」を見つけるツールの実装を行いました。性能バグとは,例えばノード間でのロックが伝播することで,時間制約がある処理の開始が閾値を超えて遅延するバグなどのことです。ハートビートの処理内でロックの取得が遅れると,規定時間以内にハートビートを送信できずノードが故障判定を受けてしまいます。修士論文研究の一環とのことで今後の発展も楽しみですね。

広瀬 智之さん「x86_64での自作OS」

speakerdeck.com

広瀬さんは 2019 年 10 月 1 日からラボユースで活動しています。メンターは光成です。

広瀬さんは,現代の PC で動かせる OS を作るという目標で,x86-64 向けの OS を開発してきました。これから OS を作って学ぼうとする人の参考になるようにドキュメントが整備された OS を作るのが目標だそうです。発表会では,現在完成している機能の中からスレッドを使った並行処理の仕組みを説明しました。

松井 誠泰さん「組み込みRTOS向け内部DSL / Go言語をnil安全にする静的解析ツールの開発」

docs.google.com

松井さんは 2019 年 11 月 22 日からラボユースで活動しています。メンターは川合です。

松井さんは TypeScript を用いて T-Kernel2.0 上で動作するプログラムを生成する言語処理系を開発しました。Docker 上の QEMU で T-Kernel を動かすことで,組み込み向けプログラムであってもテストを自動化できるよう工夫しました。

もう 1 つのテーマである Go 言語に対する静的解析器は,偽陽性が多いなどの問題はありつつも,Nil 安全でないプログラム片を検出できる検査器を開発できたようです。

第 10 期サイボウズ・ラボユース

第 10 期サイボウズ・ラボユースの開催が決定し,2020 年 4 月から募集を開始しています。 前期から募集テーマが 1 つ増えました。是非ご応募ください!

サイボウズ・ラボユース募集要項

チームで行うリモートワークに対する7つの工夫

$
0
0

こんにちは、フロントエンドエキスパートチーム@sakitoです。

今回はフロントエンドエキスパートチームがリモートワークで工夫している内容や、解決してきた課題を共有したいと思います。

最近リモートワークをはじめた会社やチームにとって参考になれば幸いです!

※ テレワークやリモートワークという言い回しがありますが、本記事内ではすべてリモートワークで統一しています。

所属しているチームと会社の状況

サイボウズには、リモートワークをいつでも行うことができる文化があります。オフィスに出社するメンバーもいれば、フルリモートのメンバー、拠点の異なるメンバーもいます。

現在、チームメンバーは 7 人いて、その内訳は東京 4 人、大阪 1 人、愛媛 1 人、福岡 1 人となっており、チームメンバーの働く場所は複数にわかれています。

働く場所がわかれてるのもあり、日々の業務のやり取りは自然とオンライン上で行うのが中心となります。こういった状況から、チームとしてリモートで業務を進めていくのに支障がでないように、いくつかの工夫をしています。

ここからはチームで行っている工夫の紹介をします。

工夫その 1. 朝会で雑談タイムを設ける

チームでは毎日30分ほど朝会を行なっています。
内容は前日の振り返りやその日のタスクを確認するだけですが、このときに雑談する時間を意識的に設けています。

この雑談の時間を前半に15分ほど設けることで、日々直接話すことができない状況でもコミュニケーションを取ることができています。
また、さまざまな理由から開始時間に遅れて入ってくるメンバーへのフォローにもなります。

工夫その 2. 週に 1 度チームでリモートランチを行う

週に 1 度チームメンバー全員でリモートランチをする機会を設けています。 話す内容は仕事に関する話題以外にも、メンバーの近況報告やフロントエンドに関する情報などさまざまです。

オンラインでのコミュニケーションは、朝会の雑談とこちらのチームランチが主なものになります。
オフラインと比べてテキストコミュニケーションが多くなるリモートワークでは、顔を見ながら小さなコミュニケーションを重ねて信頼関係を構築することが大事だと思っています。

ランチをしている風景
ランチをしている風景

工夫その 3. 離席、出勤、退勤時はチャットで共有する

サイボウズではさまざまな働き方を選択できます。

開始や終了の時間が異なるメンバーがいることもありますが、リモートではメンバーが現在在席しているのか離席しているのかが把握できないこともあります。対策として、作業開始や作業終了、私用で離席するときなどのタイミングで、離れることをチャット(Slack)で共有しています。

出退勤を報告しているslackの画像
実際のslackの画像、ゆるく運用してます

工夫その 4. ミーティングは全員オンラインで参加する

オフラインで直接集まりつつリモートで参加する人がいるという構図のミーティングでは、オフライン側が中心で話が進みがちになってしまい、リモートの参加者が発言しにくい状況になりやすいです。
そのため、オフラインで参加する人数が何人いても、全参加者がオンラインでつないでミーティングを行うようになりました。こうすることで、全員が同じ状態で発言できるようになり、非対称性を解消しています。

(隣の席のメンバーとオンラインで話す状況もよくあります。)

工夫その 5. 定期的にチームメンバーと直接顔を合わせる

弊チームのメンバーは日本全国に散らばっていますが、全員で直接顔を合わせてコミュニケーションを取るために 3 か月に 1 度、約 3 日の出張を行っています。
このタイミングが丁度 1Q の締めになるので、1 日かけてチームについて議論を重ね、次の Q に向けての準備を行います。

そのほかにも出張先の地域の名物を食べに行き、一緒に観光をしています。直接顔を合わせることで、普段のリモートでの作業も心理的にやりやすくなっていると感じます。

※ 今年に入ってからは昨今の事情もあり、出張は行っていません。

フロントエンドエキスパートチームメンバーが集合している様子
度々顔を合わせてわいわいしてます

工夫その 6. 作業ログを残す

チームメンバーはそれぞれがさまざまなタスクを行なっています。
このタスクを個人で進める際に作業ログを残せる場所を用意しています。

作業ログを残すことで、タスクをどのように進めたか、どのような背景で意思決定を行ったかなどが共有できます。また、困っている、悩んだことを残すことでチームメンバーが非同期的にその情報を拾ってフォローすることもできます。

この作業ログは、ほかのメンバーが関連タスクを行う場合に現状把握をするために見返すのにも使えます。

工夫その 7. モブプログラミングで開発・資料作り

サイボウズでは、基本的にタスクの消化をモブプログラミング(ドライバーとナビゲーターに別れて一緒にタスクに取り組むスタイル)で行うことが多いです。
モブプログラミングの手法や工夫については、kintone チームが書いたこちらの記事(リモート・モブプログラミングという働き方)がありますので、モブブログラミングとは?という方はこちらをご覧ください。

モブプログラミングでは画面をみんなで見ながら議論しつつ 1 つのものを作り上げていくので、意思決定の合意が取りやすくなります。

開発以外にもドキュメントなど、チームとして必要なものを作るときや、個人で作っているものに対して社内の有識者にみてもらいたい場合にモブをするなど、全社的にモブでやっていく文化がサイボウズ内で育ってきていると感じています。

そのほか

マイクから入力される音声が聞こえづらいことや、キーボードの打鍵音などの雑音が入ってしまうと会話がしづらいので、可能な限りマイク付きのヘッドホンがあるといいです。

サイボウズではチーム以外の取り組みでも、リモートワークに対するさまざまな取り組みをしているので、こちらも参考にしてください!

おわりに

今回はリモートワークを進めるうえで工夫していることを紹介しました。

紹介したように、リモートワークに対するチームとしての工夫はコミュニケーションに対するものが多くなっています。
チームでは毎週 KPT を行い、問題が起きてたらなにかしらの改善を見つけて、翌週には実践するようにしています。この過程で様々な工夫が生まれてきました。

今回紹介したフロントエンドエンドエキスパートチーム以外のチームでも、様々な取り組みがサイボウズでは行われているので、他のチームから紹介されるのも楽しみにしています!

会社がリモートワークをできる制度と設備や環境を用意してくれのなら、あとは個人、チームでどれだけリモートを活用できるのか工夫していきたいですね。

複雑怪奇な nginx を Go と Docker でユニットテストする

$
0
0

全国の nginx 職人のみなさま、こんにちは。野島(@nojima)です。

私の所属するYakumoプロジェクトでは、nginx を Go と Docker によってユニットテスト1しています。 手元で簡単に実行でき、ブランチへのpushのたびにCIでテストされるので、非常に便利です。 この記事では、このnginxのユニットテストについて紹介してみたいと思います。

背景

nginx は極めて柔軟なロードバランサであり、プロダクション環境ではその柔軟さを生かして多彩な役割を担っています。 我々の nginx は、ユーザーからのリクエストを AP サーバーに振り分け、アクセス制限を行い、リクエストをリダイレクトし、HTTPヘッダを付与したり削ったりしています。 しかし、nginx は便利な反面、その設定は極めて複雑になり、読解したり変更したりするのが難しくなっています。 そこで、nginx をユニットテストする仕組みを Go と Docker で作りました。

ユニットテストを導入する前は、nginx の動作確認にはデータセンターへのデプロイが必要でした。 デプロイには結構時間がかかるので、動作確認はとても面倒でした。

ユニットテストの導入により動作確認は非常に効率化されました。 ユニットテストは他のサーバーに依存していないので、ローカル環境やCI環境でも実行できます。 よって、データセンターにデプロイすることなく手元でサクッと動作を確認できますし、トピックブランチを CI で常にテストしておくこともできます。

テストはすべて Docker の内側で行われるので、ローカル環境に特殊なセットアップをしておく必要はありません。 Docker がインストールされていれば動きます。

サンプルコード

この記事のために説明用のサンプルコードを用意しました。 ローカルに clone してきて make testするとテストを実行できます。

https://github.com/cybozu/SAMPLE-test-nginx-with-go-and-docker

テスト対象の nginx

サンプルコードでは、テスト対象となる nginx は以下のような設定になっています(一部抜粋)。 APサーバーにリバプロするエンドポイントや、常に 400 を返すエンドポイントなどがあります。

server {
    listen 80;

    location / {
        proxy_pass http://${AP_SERVER_ADDR};
        ...
    }

    location /secret/ {
        deny all;
    }

    ...
}

注目してほしいのは、proxy_passの部分です。 APサーバーのアドレスを直接設定ファイルに埋め込むのではなく、AP_SERVER_ADDRという環境変数に切り出しています。 これは、テストする際にAPサーバーをモックサーバーに置き換える必要があるためです。

切り出した環境変数は、コンテナ起動時に envsubstで具体的な値に展開します。 サンプルコードでは entrypoint というシェルスクリプトがその作業をやっています。

サンプルコードでは環境変数は AP_SERVER_ADDRのみでしたが、テスト環境と実環境で値が変わる設定項目があればすべて環境変数に切り出しておきます。 例えば、resolverを使っている場合、テスト環境では Docker の DNS サーバーである 127.0.0.11を指定しないといけないので、環境変数に切り出します。

テストの概観

テストコードの説明に入る前に、テストの概観を図を使って説明します。 ローカル環境でも CircleCI 環境でも実行できるようにするために、テストは以下のような構成になっています。

テストのアーキテクチャ

太い青枠で囲われた部分が Docker コンテナを表しています。nginx-tester と nginx という2種類のコンテナがあります。

  • nginx-tester

    • go test -v ./...を実行するコンテナです。
    • テストの実行には Go と Docker が必要なので、nginx-tester のイメージには circleci/golang:1.14を使っています。
    • nginx-tester は必要に応じて nginx コンテナを起動します。
    • コンテナの中から別のコンテナを起動するために Docker outside of Dockerの構成を取っています。つまり、ホストの /var/run/docker.sockをコンテナ内にマウントすることで、コンテナからホストの docker を操作できるようにしています。
  • nginx-xxxxxx

    • テスト対象となる nginx を格納しているコンテナです。
    • コンテナ名の -xxxxxxの部分は実際にはランダムな文字列です。これは、同時に複数個起動したときに名前が被るのを防ぐためです。

nginx-tester と nginx が相互に通信できるようにするために、nginx コンテナと nginx-tester は同じ docker network に所属しています。 この docker network はテストの起動前にシェルスクリプトで作成しておきます

AP のモックサーバーは独立したコンテナではなく、nginx-tester 内の goroutine として起動します。図の破線で囲われた部分が goroutine を表しています。

次に CircleCI 上でどのようにテストを実行するかを説明します。 CircleCI では、setup_remote_dockerを使ってリモート環境をプロビジョンできます。 テストはこのリモート環境の中で実行されます。

CircleCIでの実行の様子

CircleCI でこのテストを走らせるにあたって、注意しないといけないのは次の2点です。

  1. リモート環境と Primary Container (config.ymlに書かれたステップを実行するコンテナ)の間では、セキュリティ上の理由から、HTTP や TCP などの通信が行えません。よって、リモート環境との通信は dockerコマンドを用いたものに限定されます。
  2. リモート環境では、Primary Container 上のファイルシステムが見れません。つまり、リモート環境からはソースコードが見えないということです。

実は、nginx-tester をローカルで直接実行せずにわざわざコンテナ内で実行していたのは、1 の問題を回避するためでした。

2 の問題に関しては CircleCI の公式ドキュメントに回避策が載っています。 以下の手順を行うことで、Primary Container から nginx-testerにソースコードを渡すことができます。

  1. nginx-test-dataという、空のボリュームを持つダミーのコンテナを作成する。
  2. docker cpでソースコードをダミーコンテナに転送する。
  3. docker run--volumes-fromオプションを使って、ダミーコンテナのボリュームをマウントする。

さて、テストの概観がわかったところで実際のテストコードを見ていきましょう。 まずはより簡単なリバプロなしの場合から始めます。

テストコード (リバプロなし)

次のテストは GET /secret/で 400 が返ってくることを確認するものです。

func TestSecretEndpoints(t *testing.T) {
    t.Parallel()

    nginx := StartNginx(t, NginxConfig{}) // ①defer nginx.Close(t)
    nginx.Wait(t)

    resp, err := http.Get(nginx.URL() + "/secret/") // ②if err != nil {
        t.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusForbidden { // ③
        t.Errorf("status code should be 400, but %d", resp.StatusCode)
    }
}

StartNginx()は nginx コンテナを起動する関数です。詳細は後述します。 次に nginx.Wait()で起動が完了するまで待ちます。

② nginx に対して GET /secret/を行います。http.Get()は単なる Go の標準関数です。

③ レスポンスを assert します。これも普通の Go のテストコードです。

前節では Docker を使ってテストすると説明しましたが、Docker はこのテストケースのコードからは完全に隠蔽されています。 これは、Docker にまつわる複雑さをテストケースから分離することで、テストケースを読みやすくするためです2

Docker を実際に操作しているのは StartNginx()nginx.Close()などの関数です。 それでは StartNginx()の実装を見ていきましょう。

StartNginx()

StartNginx()の肝となる部分は以下のコードです。この関数の主な仕事は docker コマンドを叩くことです。

// docker コマンドを叩いて sample-nginx:latest を起動する。
args := []string{
    "run", "--rm",
    "--name", name,
    "--net", network,
    "-e", fmt.Sprintf("AP_SERVER_ADDR=%s", config.APServerAddress),
    "sample-nginx:latest",
}
cmd := exec.Command("docker", args...)
if err := cmd.Start(); err != nil {
    t.Fatal(err)
}

このコードで注目してほしい点は、-eAP_SERVER_ADDRに値を渡していることです。 これにより、任意のAPサーバーを差し込んで nginx を起動できるわけです。 それでは、実際にAPサーバーを差し込むテストを見ていきましょう。

テストコード (リバプロあり)

次のテストは AP サーバーをモックしてリバプロの挙動を確認するものです。

func TestReverseProxy(t *testing.T) {
    t.Parallel()

    ap := StartMockAP(t) // ①defer ap.Close(t)

    nginx := StartNginx(t, NginxConfig{ // ②
        APServerAddress: ap.Address(),
    })
    defer nginx.Close(t)
    nginx.Wait(t)

    resp, err := http.Get(nginx.URL() + "/") // ③if err != nil {
        t.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        t.Errorf("status code should be 200, but %d", resp.StatusCode)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        t.Fatal(err)
    }
    ifstring(body) != "I am AP server" {
        t.Errorf("unexpected response body: %s", string(body))
    }
}

StartMockAP()でモックAPを起動しています。

StartNginx()で nginx コンテナを起動します。ここでモックAPのアドレスを差し込んでいることに注目してください。

③ AP と nginx を起動できたら、あとはもう普通のテストです。リクエストを送り、レスポンスを普通に assert しましょう。

MockAP を Nginx に差し込んでいる部分は OOP における Dependency Injection に相当します。 普通、Dependecy Injection は同じプロセス内のオブジェクトに対して行うのですが、この例だと別のプロセスに対して依存オブジェクトを差し込んでいる形になっているのが面白いところです。

それでは最後に StartMockAP()の実装を見てみましょう。

StartMockAP()

StartMockAP()は以下のようになっています(一部説明に不要な部分を省略しています)。 ポートを自動的に選ぶためにちょっと特殊なことをしていることを除けば、単に goroutine で HTTP サーバーを立てているだけです。

// 空いているポートを自動的に選ぶ
l, err := net.Listen("tcp", ":0")
if err != nil {
    t.Fatal(err)
}

handler := func(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("I am AP server"))
}
ap := &MockAP{
    host: host,
    port: l.Addr().(*net.TCPAddr).Port,
    server: &http.Server{
        Handler: http.HandlerFunc(handler),
    },
}

// 別の goroutine でサーバーを走らせるgofunc() {
    if err := ap.server.Serve(l); err != nil&& err != http.ErrServerClosed {
        t.Log(err)
    }
}()

なお、標準ライブラリの httptestStartMockAPと同じようなことができますが、httptestはアドレスを 127.0.0.1にバインドしてしまうので、今回のユースケースでは利用できません。

今回のサンプルコードでは一種類の MockAP しか実装されていませんが、私達が実際に使っているテストでは様々な MockAP が実装されています。 例えば、レスポンスを一切返さない MockAP や、不正な SSL 証明書を持つ MockAP などがあります。 これらの MockAP を使うことで、手動で起こすのが面倒なケースをテストすることができます。

まとめ

Go と Docker を使って nginx をテストする仕組みを紹介しました。 この仕組みは、我々のプロダクション環境における nginx を支えています。

この記事がみなさまの nginx ライフの一助となれば幸いです。


  1. ここでは nginx をひとつのユニットとみなしています(ユニットテストにおける「ユニット」が何を指すかは定義によって異なっており、統一されていません。この記事では nginx がひとつのユニットとなるような定義を採用したと解釈してください)。

  2. テストコードでは関心の分離や単一責任の原則が蔑ろにされることがよくありますが、私はテストコードでもこれらの原則は重要だと思っています。

LernaとYarn WorkspacesでMonorepo管理

$
0
0

こんにちは、フロントエンドエキスパートチームの小林(@koba04)です。

本記事では、Lerna と Yarn Workspaces を使った Monorepo 管理について解説します。

Monorepoとは

本記事では、単一のリポジトリで複数のモジュールやパッケージ(今回の場合は npm パッケージ)を管理する手法を Monorepo と呼んでいます。

有名なところだと、BabelJestCreate React Appなどが後述する Lerna を使い複数パッケージを単一のリポジトリで管理しています。 他にも Reactも Lerna は使っていませんが単一リポジトリで複数パッケージを管理しています。

また、上記のようなライブラリ以外にも企業で利用している npm パッケージを Monorepo として管理している例もあります。下記は Shopify の例です。 packages/ディレクトリ以下を見ると、ASTの utility から React 関連のライブラリまで色々あることがわかります。

https://github.com/Shopify/quilt/tree/master/packages

単一のリポジトリで複数パッケージを管理するメリット、デメリットには下記のような点があります。

メリット

  • 依存関係のある複数パッケージの開発が簡単
    • 都度パッケージをリリースしたり、npm linkをする必要がないため
  • 依存パッケージの管理をまとめてできる
    • Renovateなどを使い依存パッケージのバージョンを管理している場合には特に楽

デメリット

  • Issue や PR をすべて 1 つのリポジトリで管理する必要がある
  • Monorepo での開発ワークフローを理解している必要がある

サイボウズでは、すでに kintone のプラグインやカスタマイズ開発を行うための npm パッケージをいくつも提供しており、これらは現状個別のリポジトリで管理されています。 しかしながら、Renovate による依存パッケージの更新対応コストや、お互いに依存関係のあるパッケージの開発など、リポジトリが分かれていることによるデメリットがありました。

現在、新しく kintone のプラグインやカスタマイズの開発体験を向上させるためのツール開発を行っており、これを機会に Monorepo に移行することにしました。 開発体験を向上させる取り組みについては、別記事として紹介したいと考えています。

実際に適用しているリポジトリは下記より確認できます。 現在は 2 パッケージのみですが、今後どんどん増えていく予定です。

https://github.com/kintone/js-sdk

まず最初に、Lerna と Yarn Workspaces について簡単に紹介します。

Lerna

https://lerna.js.org/

Lerna は ”A tool for managing JavaScript projects with multiple packages.” と公式サイトにある通り、複数パッケージを管理するためのツールです。

Lerna は、一例として下記のような機能を提供します。

  • 複数 npm パッケージを単一リポジトリで一元管理
  • lerna bootstrapコマンドを使った Monorepo すべての依存パッケージを一括インストール
  • 重複した依存パッケージの hoisting(巻き上げ)
    • hoisting とは、パッケージ間で重複している依存パッケージをそれぞれ別にインストールするのではなく、親となるディレクトリにインストールして共有することです
  • lerna publishコマンドを使った、変更があるパッケージの一括 npm publish
    • すべての npm パッケージのバージョンを同一にするか、個別に管理するか(independent mode)を選択可能
  • lerna runコマンドを使った Monorepo で管理しているパッケージが持っている npm-scripts の一括実行
  • 既存の git リポジトリから Monorepo へのインポート

このように、Lerna は複数パッケージをまとめて扱うための機能を提供しています。

Yarn Workspaces

https://classic.yarnpkg.com/lang/en/

Yarn は JavaScript のパッケージマネージャです。 Node.js が標準で提供している npm と同様に、npm パッケージの管理ができます。 パッケージマネージャとしての Yarn と npmの違いについては本記事では言及しないので、下記を確認ください。

https://classic.yarnpkg.com/en/docs/migrating-from-npm

※本記事では、Yarn v1 を使用します。

Yarnは Workspaces として、複数の npm パッケージを管理する機能を持っています。 これは npm にはない機能です。

https://classic.yarnpkg.com/en/docs/workspaces

注:npm は v7 で Monorepo サポートを計画しています。下記は RFC です。
https://github.com/npm/rfcs/pull/103

Yarn Workspaces を使うことで下記のようなことが可能です。

  • 複数 npm パッケージを単一リポジトリで一元管理
  • yarn installコマンドを使った Monorepo すべての依存パッケージを一括インストール
  • 依存パッケージの hoisting
  • yarn workspacesコマンドを使った Monorepo で管理しているパッケージが持っている npm-scripts の一括実行
  • 単一のyarn.lockファイルですべての依存関係を管理

上記の通り、Yarn Workspaces と Lerna は同じような機能を持っています。

その違いとして、

Yarn’s workspaces are the low-level primitives that tools like Lerna can (and do!) use

とある通り、Yarn Workspaces の方がより低レベルなパッケージマネージャとして機能を提供しています。

LernaとYarn Workspacesを一緒に使う

Lerna は Yarn Workspaces と一緒に使うためのオプションを提供しています。 Lerna の設定ファイルである、lerna.json"npmClient": "yarn"と指定することで、内部的に npm の代わりに Yarn を使用できます。

実際、多くの Lerna を使っているプロジェクトでは Yarn Workspaces と組み合わせて使っていることが多いです。 ただ、ここで一つ疑問が湧きます。

「 Lerna で Yarn Workspaces が提供している機能はカバーできるが、Yarn を使うメリットはあるのか?」

実際のところ、Lerna と npm だけでも多くの場合はやりたいことを実現できます。
ただ、Yarn はパッケージマネージャ本体で Monorepo をサポートしていることもあり、Monorepo であることを利用者にあまり意識させない形での Monorepo 管理を可能にしてくれます。
例えば下記のような点です。

パッケージのインストール

全パッケージの依存パッケージのインストールはリポジトリのルートで yarn installするだけであり、単一パッケージのリポジトリと同様です。個別のパッケージで依存パッケージをインストールする場合も、対象パッケージのディレクトリで yarn add {package-name}コマンドを実行するだけです。

単一の yarn.lockで管理できる

Yarn Workspaces では yarn.lockファイルはリポジトリのルートにのみ作成され、それぞれのパッケージ毎に作られません。そのため依存パッケージのアップデートの際に発生する差分は最小限になります。

使い方

ここでは、Lerna と Yarn Workspaces を使った Monorepo 運用のための手順を記載します。

セットアップ

リポジトリのルートにある package.json"private": trueを設定します。また Yarn Workspaces のための設定をします。 workspacesには、パッケージ用のディレクトリだけでなく examplesのようなサンプルのためのディレクトリも対象に含めることも可能です。 kintone/js-sdkでは、デモやサンプルを配置するために examplesディレクトリを使用しています。

// package.json
// workspacesのフィールドを追加
{
    :
    "private": true,
    "workspaces": [
        "packages/*",
        "examples/*"
    ],
    :
}

Lerna をインストールして lerna initを実行します。

% yarn add --dev lerna
% yarn lerna init --independent
lerna notice cli v3.19.0
lerna info Updating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files
✨  Done in0.98s.

今回はそれぞれの npm パッケージ単位で個別にバージョンを管理したいので、--independentを指定します。 設定しない場合、すべての npm パッケージのバージョンが同一バージョンになります。

生成された Lerna の設定ファイルである lerna.jsonに Yarn Workspaces のための設定をします。

{
    "npmClient": "yarn", // 追加
    "useWorkspaces": true,
    "version": "independent"
}

Lerna は useWorkSpacesというオプションを持っています。 これを利用することで Yarn Workspaces のワークスペース設定をそのまま Lerna でも利用できます。

もちろん Lerna と Yarn Workspaces で対象パッケージを分けることも可能です。 この方法は Jest でも採用されており、Jest では Yarn Workspaces で packageswebsiteexamplesとして管理しており、 Lerna では packagesのみ管理しています。

今回は、lernaコマンドで examplesディレクトリも統一的に扱いたいため、useWorkSpacesオプションを使用します。

新規パッケージの追加

新しく開発するパッケージを Monorepo に追加する際は、 lerna createコマンドを使います。

% npx lerna create @kintone/rest-api-client

npm パッケージのインストール

npm パッケージをインストールする場合は、インストールしたいパッケージのディレクトリで通常通り yarn add package-nameするか、yarn workspaceコマンドを使ってインストールします。

% cd packages/rest-api-client
% yarn add form-data
# or
% yarn workspace @kintone/rest-api-client add form-data

各パッケージで同一の依存ライブラリをインストールした場合、package.jsonはインストールしたパッケージのものが更新されますが、yarn.lockはルートにあるものが更新されて、インストールもルートの node_modules/に対して行われます。

共通 npm パッケージのインストール

TypeScript などすべてのパッケージで使うような devDependencies については、ルートで入れておくと個別にインストールする手間を省くことができます(依存関係が各 package.jsonからわからなくなるというデメリットはありますが)。 ルートに共通で使うパッケージとしてインストールする場合には、インストールのオプションに明示的に -Wオプションを付ける必要があります。

% yarn add -W--dev typescript prettier eslint

パブリッシュ

パッケージのパブリッシュは lerna publishで行います。
lerna publishはデフォルトでは CLI 上でリリースするバージョンを指定しますが、Conventional Commitsの形式で commit している場合には、--conventional-commitsオプションを利用することで commit log からバージョンを決定できます。この際、CHANGELOG.mdも自動生成されます。

% npx lerna publish --conventional-commits
lerna notice cli v3.19.0
lerna info versioning independent
lerna info Looking for changed packages since @kintone/rest-api-client@1.0.0
lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular"

Changes:
    - @kintone/rest-api-client: 1.0.0=>1.1.0
    - @kintone/cutomize-uploader: 3.0.1=>3.0.2

? Are you sure you want to publish these packages? (ynH)
:
Successfully published:
    - @kintone/rest-api-client@1.1.0
    - @kintone/customize-uploader@3.0.2
lerna success published 2 packages

新しくパッケージを npm に publish する場合は下記コマンドで publish できます。

% npx lerna publish from-package --conventional-commits

examples以下にあるパッケージのように npm にパブリッシュしたくないパッケージには、package.json"private": trueを指定します。

Clone後のセットアップ

git clone した後のセットアップは Lerna 単体の場合、独自の lerna bootstrapコマンドを利用する必要がありますが、Yarn Workspaces を使う場合には通常のリポジトリと同様にリポジトリのルートで yarn installを行うだけです。

% yarn install

まとめて npm-scripts を実行する

lerna runというコマンドを使うことで、指定した npm-scripts を Lerna で管理しているすべてのパッケージに対して一括で実行できます。
例えば、すべてのパッケージに testの npm-scripts があることを想定して一括で実行したい場合には、リポジトリのルートで lerna run testを実行します。 --streamオプションをつけることで、それぞれの npm-scripts の出力を表示できます。

kintone/js-sdkで実行すると下記の通りです。

% npx lerna run test--stream
lerna notice cli v3.20.2
lerna info versioning independent
lerna info Executing commandin2 packages: "yarn run test"
@kintone/customize-uploader: yarn run v1.22.4
@kintone/customize-uploader: $ jest --rootDir src
@kintone/customize-uploader: PASS src/__tests__/init.test.ts
@kintone/customize-uploader: PASS src/__tests__/util.test.ts
@kintone/customize-uploader: PASS src/__tests__/import.test.ts
@kintone/customize-uploader: Test Suites: 4 passed, 4 total
@kintone/customize-uploader: Tests:       7 passed, 7 total
@kintone/customize-uploader: Snapshots:   0 total
@kintone/customize-uploader: Time:        1.948s, estimated 2s
@kintone/customize-uploader: Ran all test suites.
@kintone/customize-uploader: Done in2.95s.
@kintone/rest-api-client: yarn run v1.22.4
@kintone/rest-api-client: $ jest --rootDir src
@kintone/rest-api-client: PASS src/client/__tests__/BulkRequestClient.test.ts
@kintone/rest-api-client: PASS src/__tests__/KintoneAllRecordsError.test.ts
@kintone/rest-api-client: PASS src/__tests__/url.test.ts
@kintone/rest-api-client: PASS src/client/__tests__/File.test.ts
@kintone/rest-api-client: PASS src/__tests__/KintoneRestAPIError.test.ts
@kintone/rest-api-client: PASS src/__tests__/KintoneRestAPIClient.test.ts
@kintone/rest-api-client: PASS src/__tests__/KintoneRequestConfigBuilder.test.ts
@kintone/rest-api-client: PASS src/client/__tests__/RecordClient.test.ts
@kintone/rest-api-client: PASS src/client/__tests__/AppClient.test.ts
@kintone/rest-api-client: Test Suites: 9 passed, 9 total
@kintone/rest-api-client: Tests:       243 passed, 243 total
@kintone/rest-api-client: Snapshots:   0 total
@kintone/rest-api-client: Time:        3.37s
@kintone/rest-api-client: Ran all test suites.
@kintone/rest-api-client: Done in3.86s.
lerna success run Ran npm script 'test'in2 packages in7.5s:
lerna success - @kintone/customize-uploader
lerna success - @kintone/rest-api-client

npm-scripts の一括実行は、yarn workspaces run testのように Yarn Workspaces が提供するコマンドでも可能です。

それぞれのパッケージが testlintなど決まった名前で npm-scripts を用意しておくことで、コマンド 1 つで確認が可能になるため、複数パッケージのメンテナンスコストを下げることができます。

kintone/js-sdkでは、下記のような npm-scripts を定義することをルールとしています。

https://github.com/kintone/js-sdk/blob/master/CONTRIBUTING.md#create-a-new-package

  • build … TypeScript のコンパイルなどの成果物を生成する処理
  • lint … ESLint などの Lint 処理
  • test … テスト
  • test:ci … CI 上で実行するテスト
  • prerelease … パッケージのリリース前に行いたい処理

上記はルールだけだと漏れてしまうことが容易に想像できるため、上記の npm-scripts をすべて実装しているかを下記のテストでチェックしています。

https://github.com/kintone/js-sdk/blob/a603caabadc695a34f3202eb45699e505b58eb80/tests/npmScripts.test.ts

既存リポジトリからの移行

途中で Monorepo 運用に変える場合、既存のリポジトリを Monorepo での管理に移行する必要があります。Lerna は import コマンドという既存のリポジトリを Monorepo に移行するためのコマンドを提供しています。これを利用することで、元のリポジトリの commit log を残したまま Monorepo に移行できます。 実行方法は、lerna import path/to/targetと元のリポジトリを指定するだけです。

ただし Troubleshootingにもある通り、Merge conflict commits があるリポジトリの場合は import に失敗してしまいます。

これは Troubleshooting に従って、--flattenオプションをつけることで import 可能ですが、この場合はMerge commit 単位で commit がまとめられてしまいます。 Merge commit 単位でまとめて欲しくない場合は、一度リポジトリの commit をすべて rebase した状態にすることで import 可能な状態にできます。 @kintone/rest-api-clientkintone/js-sdkへの移管のタイミングで上記の方法で import しました。

パッケージを import した後は、最後にリリースした commit に対して package-name@versionの形式で GitHub に tag を push する必要があります。 これにより、 Lerna は次回のリリース時にこの tag を起点として差分を検出します。

TypeScriptの型定義

TypeScriptを使っている場合には、型定義パッケージ (@types/xxx) をインストールして利用します。
その際、インストールされるパッケージが hoisting されることによって型定義が node_modules/に見つからずエラーになってしまいます。 そのため、compilerOptionstypeRootsにリポジトリのルートにある node_modulesを追加する必要があります。

"typeRoots": [
    "node_modules/@types",
    "../../node_modules/@types",
],        

ts-nodeを使ったサンプルパッケージの実行

現在 examplesのディレクトリには、rest-api-clientのサンプルスクリプトを管理するパッケージがあります。
このパッケージは元々 rest-api-client本体に含まれていたものを切り出したものです。 以前は ts-nodeで直接実行していたのですが別パッケージとして切り出した結果、スクリプトの実行に rest-api-clientのコンパイルが都度必要になってしまいました。これまで通りの体験でスクリプトを実行するために、TypeScript の Compiler Options である pathsを使いリンクすることで、別パッケージに切り出した後も引き続き ts-nodeコマンドでコンパイルせずに実行できるよう対応しました。

"baseUrl": "../../",
    "paths": {"@kintone/rest-api-client": ["packages/rest-api-client/src"]},       

ts-nodepathsオプションを有効にするために tsconfig-pathsを使用しています。

おわりに

今回は、Lerna と Yarn Workspaces を組み合わせて効率的に複数の npm パッケージを管理する方法を紹介しました。
今回は紹介できませんでしたが、今後パッケージが増えてお互いに依存関係を持つような状態に備えて、TypeScript の Project Referencesの導入も検討しています。

サイボウズでは、Web アプリケーションのフロントエンドだけでなく、プラットフォームのためのツール作りを OSS でやりたいというエンジニアも絶賛募集中です!

https://cybozu.co.jp/company/job/recruitment/list/front_end_expert.html

Cybozu Tech Meetupスタートします

$
0
0

こんにちは、Yakumoチーム兼コネクト支援チームの@ueokandeです。 コネクト支援チームは、「社内のエンジニアと社外のエンジニアのコネクトを支援する」ことを目的に活動しています。 この度『Cybozu Tech Meetup』という技術発信イベントをスタートすることにしました。

Cybozu Tech Meetupのロゴ画像

Cybozu Tech Meetupとは

Cybozu Tech Meetupは、社外の方にサイボウズのエンジニアや技術について知ってもらおうと思い、スタートしました。 このイベントでは、現場で働くエンジニアが普段の活動や開発・運用で得た知見などをお届けします。 またコネクト支援チームとしても、社内のエンジニアにもっと外へ飛び出してもらいたいという狙いもあります。

これまでに「Cybozu Meetup」というオフラインイベントを定期的に開催してきましたが、この度「Cybozu Tech Meetup」という名に改めました。 開催場所もオンライン、オフラインは問わず柔軟な開催をしていきたいと思います(が、暫くはオンライン開催のみになるでしょう)。

ここ数週間、新型コロナウイルスの影響で外出自粛要請が出されて、各イベントの中止も続いています。 そんな情勢だからこそ、技術発信をしてエンジニア界隈を元気づけていきたいという思いがあります。 そこでCybozu Tech Meetupの企画・開催に至りました。

第1回目のテーマは『kintone開発チーム』

記念すべき第1回目のテーマは『kintone開発チーム』ということで、kintone開発チームの内情についてお話しします。 kintoneはサービス開始から今年で9年目。開発プロセスも工夫を重ねてきました。 kintone開発チームはプロダクトマネージャー、プログラマー、デザイナー、QA、プロダクトライターのメンバーで構成されており それぞれ別の視点から開発に携わっています。

トークセッションは以下のとおりです。

  • kintone × リモートモブプログラミング / 西 大樹
  • ひよっこ kintone 開発プログラマーの冒険譚 / 中川 遼太郎
  • IT業界未経験者が kintone QA として1年間がんばった話 / 川畑 ひとみ

今回の開催はYouTube Liveによる配信を予定しています。 このイベントへの参加や、詳しいイベント情報は告知ページからどうぞ!

cybozu.connpass.com

おわりに

Cybozu Tech Meetupは今後も定期的に開催する予定で、それぞれの回で異なるチームにフォーカスします。 社内には製品開発チームだけでなく、横断的に活動しているチームなどもあり、それぞれのチームごとに異なる特色や知見があります。 次回以降の開催もご期待ください!


エンジニアインターンシップ2020を開催します!

$
0
0

こんにちは!PSIRT兼エンジニアインターン運営チームの純平です。

サイボウズでは毎年夏に、エンジニア/デザイナー向けサマーインターンシップを開催しています。 今年は新型コロナウイル(COVID-19)の影響で一部実施方法を変更して開催いたします。

インターン2020ロゴ

今年のインターンは「ココ」が違う

昨年までは、全開催コースを同時開催しておりましたが、 コンテンツの価値を最大化し、参加者の皆様により意義のあるものとするため、各コース異なる時期・期間で実施いたします。

また、新型コロナウイルスへの対策として、皆様に安心して参加していただけるよう、 フルリモートで行うことになりました。(もちろん、お家からでも参加OKです!)

”チーム”を支えるサイボウズの風土や雰囲気をインターンを通して感じてみませんか? 皆様のエントリー、お待ちしております!

対象

以下のすべてを満たす方

  • 2022年4月までに入社可能

  • PC, ネットワーク, ヘッドセットを用意可能

エントリー開始日

2020年4月27日(月)より随時エントリー受付開始

開催コース

各コースの内容、応募方法や報酬などの詳細は特設サイトをご確認ください。 cybozu.co.jp

※コースによって開催時期や応募期間が異なりますのでご注意ください。

参考情報

昨年までのインターンの報告も投稿しておりますので、ぜひご覧ください。 blog.cybozu.io

脆弱性報奨金制度 2020 始まります

$
0
0

こんにちは。Cy-PSIRT の長友です。2019 年度の報奨金制度では、たくさんのバグハンターの皆様にたくさんのご報告をいただきました。サイボウズ製品のセキュリティ品質向上への取り組みにご協力いただき大変感謝しています。
このエントリーでは、2020 年度の脆弱性報奨金制度についてご紹介します。

脆弱性報奨金制度 2020

サイボウズ脆弱性報奨金制度が開始して 7 年目に突入しました。今年度の制度概要は以下の通りです。 詳細につきましては、脆弱性報奨金制度のページをご確認ください。

脆弱性報奨金制度の実施期間

2020 年 4 月 24 日(金)~ 2020 年 12 月 18 日(金)
※終了日以降もご報告を受け付けますが、次年度の報奨金制度のルールが適用されます。ご注意ください。

制度のルール

制度のルールに関しては、脆弱性報奨金制度ルールブックおよび脆弱性認定ガイドラインをご確認ください。
昨年度参加された皆様は、今年度からお支払い金額の算出に関するルールが大幅に変更されている点にご注意ください。CVSSv3 基本値を金額算出の基準とするのをやめて、脆弱性の種別に応じてレンジを設定しました。報告されたレポートの傾向に応じて、脆弱性の種別は変更されるかもしれません。詳しい算出方法に関しては上記の脆弱性報奨金制度ルールブックをご確認ください。

脆弱性報奨金制度の対象製品

対象製品は以下のページに掲載されています。参加を考えられている方は必ずご確認ください。 https://cybozu.co.jp/products/bug-bounty/

なお、一部のパッケージ製品および周辺サービスが今年度の脆弱性報奨金制度の対象から外れています。昨年度参加された皆様はご注意ください。

今年度の注目ポイント

脆弱性報告用サイト

これまでは脆弱性報告をメールまたは Web フォームから受け付けていましたが、本年度から脆弱性報告を受け付けるための専用システム(以下、報告用サイト)をご用意しました。
報告用サイトのアカウント作成に関しては脆弱性報奨金制度のページをご確認ください。

プライベートプログラム

昨年度、一部のバグハンターの方のみにご参加いただけるプライベートプログラムを試験的に実施しました。今年度の開催は現時点では未定ですが、実施ができたらいいなと考えています。
開催が決定次第、対象の皆様へ個別にご連絡いたします。

報奨金最大 5 倍キャンペーン通年実施

報奨金最大 5 倍キャンペーン、今年度も実施します。
製品によって報奨金の倍率が異なります。
昨年度参加された皆様は、キャンペーン対象製品が一部変更になっていることにご注意ください。

製品 倍率
kintone
kintone モバイル
cybozu.com 共通管理
cybozu.com 運用基盤
5 倍
Garoon 2 倍
その他 1 倍

ポイント制度

こちらも、今年度も実施いたします。 ご報告いただいたもので、「認定の有無にかかわらず有益な情報だった」「レポートの内容が的確であった」など、様々な視点で「ポイント」を付与します。そして、累計獲得ポイントに応じてオリジナル T シャツなどの特典をお贈りします。
詳しい加点のルールに関しては脆弱性報奨金ルールブックをご確認ください。
特典をお贈りする条件は以下の 3 つです。

  1. 対象年度中の獲得ポイントの合計が 100pt に達した(先着順)
  2. 対象年度中の獲得ポイントの合計が 300pt に達した(先着順)
  3. 対象年度末の獲得ポイントのランキング上位 3 名に入った

それぞれ、違う特典をご用意しておりますので、奮ってご参加ください。

リアルタイムランキング

今年度も毎週金曜日に 2 つのランキングを@cybozubugbountyにて公開いたします。
どちらも更新のあった週にのみ掲載されます。

  • 報奨金額上位 5 名(お名前)
  • 累計獲得ポイント上位 5 名(お名前、累計獲得ポイント)

おわりに

改めて、2019 年度の脆弱性報奨金制度にご参加いただいたすべてのバグハンターの皆様に感謝を申し上げます。 今年度の脆弱性報奨金制度でもたくさんのご報告をお待ちしております。
なお、昨年度の報告件数や傾向などについては、集計が完了次第別エントリーにて公開を予定しております。こちらもお楽しみに。

Closure LibraryからTypeScriptの型定義を生成する

$
0
0

こんにちは、フロントエンドチームエキスパートチームの穴井(@pirosikick)です。

弊社の製品である kintone は Closure Tools (Closure Library と Closure Compiler の総称) を使って開発していますが、TypeScript を使ったモダンなスタックへの移行を検討しています。

その移行の過程で Closure Tools 側のコードを TypeScript で型安全に再利用したいケースが発生し、その解決策として Clutz というツールを試しています。

今回は、この Clutz がどういったツールなのか、その使用方法と注意点などについて紹介します。

この記事は次の条件に当てはまる方には特におすすめできる内容になっています。

  • Closure Tools(Closure Compiler, Closure Library)を使って開発しているプロダクトがある
  • Closure Tools が辛くなってきたので、TypeScript を使ったモダンなスタックに移行したい
  • Closure Tools に興味はないが、TypeScript の型情報を自動生成する CLI の活用に興味がある

※Closure Tools を脱したい人向けの記事で、「Closure Tools、激推しです!」という内容ではないのでご注意ください。

Clutz とは何なのか?

Clutz - Closure to TypeScript Declarations (.d.ts) generator.

Clutz とは、Closure Library を使って記述されたソースコードから TypeScript の型定義ファイル(拡張子が.d.tsのファイル)を出力する CLI ツールです。

イメージしやすいようにサンプルコードをもとに説明していきます。

0. 下準備

サンプルコードを用いた説明に入る前に、Clutz をインストールします。

Clutz は npm パッケージなどの利用しやすい形で提供されていません。今回は Clutz を npm パッケージとして使えるように公開している@teppeis/clutzを使います。

# @teppeis/clutzをグローバルにインストール
$ npm i -g @teppeis/clutz

# 引数を渡さずに実行するとUsageが表示される
$ clutz
No files or externs were given
Usage: clutz [options...] arguments...
(オプションが表示されるが長いので割愛)

1. サンプルコードを記述

Closure Library を使って記述されたサンプルコードを用意します。サンプルコードのコメントに番号を打っていますので、それに沿って説明します。

// src/closure/hello.js// ①ネームスペースを定義
goog.provide("app.hello");

// ②JSDoc形式で型を記述できる/** * @param {string} name */
app.hello.sayHello = function (name) {
  console.log(`Hello, ${name}`);
};

// ③グローバル変数にsayHello関数を晒す
goog.exportSymbol("app.hello.sayHello", app.hello.sayHello);

goog.provide

goog.provide関数は、ネームスペースを定義するための関数です。他のファイルでgoog.require('app.hello')と記述すると、このファイルを参照することができます。

② Closure Compiler を使った静的型チェック

Closure Compiler を使って静的型チェックを行うことができます。型は JSDoc 形式で記述します。TypeScript にも JSDoc 形式で型定義を行う機能がありますが、型の解釈やサポートしている記述方法が少し異なります。違いが気になる場合は以下の Closure Compiler のドキュメントと、TypeScript のドキュメントを見比べてみてください。

goog.exportSymbol

sayHello関数を TypeScript 側から呼び出せるようにするために、goog.exportSymbol関数を使ってグローバル変数に定義します。第一引数はネームスペースで、この例ではapp.hello.sayHelloというグローバル変数を定義しています。

2. サンプルコードから型定義ファイルを出力

サンプルコードを用意したので、Clutz で TypeScript の型定義ファイルを出力してみましょう。

clutz --partialInput src/closure/hello.jsと実行すると、標準出力に型定義が出力されます。--partialInputは、不明な型をいい感じに無視するオプションで、このオプションを指定しない場合は「console の型が分からないんだが」と怒られてしまいます。

# 標準出力に型定義を出力
$ clutz --partialInput src/closure/hello.js
//!! generated by clutz.
// Generated from src/closure/hello.js
declare namespace ಠ_ಠ.clutz.app.hello {
  function sayHello (name : string ) : void ;
}
declare module 'goog:app.hello' {
  import hello = ಠ_ಠ.clutz.app.hello;
  export = hello;
}

標準出力ではなくファイルに出力したい場合は、-o ファイルパスで指定します。

# ファイルに型定義を出力
$ clutz -o @types/closure.d.ts --partialInput src/closure/hello.js

$ cat @types/closure.d.ts
//!! generated by clutz.
// Generated from src/closure/hello.js
declare namespace ಠ_ಠ.clutz.app.hello {
  function sayHello (name : string ) : void ;
}
declare module 'goog:app.hello' {
  import hello = ಠ_ಠ.clutz.app.hello;
  export = hello;
}

3. 出力された型定義について

出力された型定義を見ると、ಠ_ಠ.clutz.app.helloと顔文字のようなネームスペースが定義されています。おそらく、他のネームスペースと絶対被らないように変な名前にしていると思われるので、あまり気にしなくて大丈夫です。

また、goog:app.helloというモジュールが定義されており、その中でಠ_ಠ.clutz.app.helloが export されています。これにより、TypeScript のコードからは、以下のようにgoog:app.helloを import する形でapp.hello.sayHelloを参照できます。

// src/ts/index.ts TypeScriptのコードimport{ sayHello }from"goog:app.hello";// 型的にはエラーは出ないが、// このままでは実行時にエラーになる
sayHello("pirosikick");

上記のコードは TypeScript による型チェックのエラーは発生しませんが、実際にはgoog:app.helloというパッケージは存在しないので、実行時または webpack 等でのビルド時に「goog:app.hello というモジュールが存在しないよ」というようなエラーになってしまいます。

出力した型定義で型エラーが発生している場合

サンプルコードがシンプルだったので発生しませんでしたが、出力した型定義にGlobalObject等の未定義の型が含まれており、型エラーが発生するケースがあります。その場合は、Clutz のリポジトリにあるclosure.lib.d.tsGlobalObject等の型が定義してありますので、そちらを TypeScript の型の評価に入る場所に置くと解決します。

4. 出力された型と実コードの紐付けを行う

実行時/ビルド時のエラーを避けるために、goog:app.hellohello.jsで定義したグローバル変数(app.hello)との紐付けを行います。webpack を利用している場合は、externalsオプションを使います。

以下のように記述すると、"goog:app.hello"というモジュールが import(commonjs の場合は require)された場合、グローバル変数のapp.helloを代わりに使ってくれます。

// webpack.config.js
module.exports = {…,

  externals: {// "goog:app.hello"は、グローバル変数のapp.helloを使う'goog:app.hello': 'app.hello'},

    …
};

あとは、Closure Tools 側の JS ファイル、TypeScript 側の JS ファイル(webpack でビルドしたファイル)の順で読み込めば、sayHello関数を TypeScript から呼び出せます。

<!-- Closure Tools側のJSファイル(Closure Compilerでビルド) --><scriptsrc="hello-build.js"></script><!-- TS側のJSファイル(webpackでビルド) --><scriptsrc="main.js"></script>

サンプルコードは以下のレポジトリに置いています。git clone して npm ciしたあとに npm startを実行することで動作を確認できます(コンソールに"Hello, pirosikick"出力されるだけのシンプルなものです)。

https://github.com/pirosikick/clutz-example

Clutz を使う上での注意点

Clutz を使う上で気をつけるべき点は以下のとおりです。

  • Clutz 自体の開発が活発とは言えない
  • 圧縮後のことを気にする必要がある

Clutz 自体の開発が活発とは言えない

Clutz のリポジトリを見ると分かりますが、開発が活発に行われているとは言い難い状況です。Clutz にずっと頼るというよりは、移行期間の一時的な利用に留めるのがよいと思います。

圧縮後のことを気にする必要がある

Closure Compiler でコードの圧縮を実行している場合、圧縮後にクラスのプロパティやメソッドの名前が変わってしまうので注意しましょう。型定義の生成には圧縮前のコードを使うので、出力された型に存在するプロパティ・メソッドが実行時に存在せず、実行エラーになってしまいます。

対策としては、メソッドの場合はgoog.exportProperty関数を使って圧縮後も同じ名前でアクセスできるように設定できます。

// SomeClassのsomeMethodを// 圧縮後も"someMethod"という名前でアクセスできるように設定
goog.exportProperty(
  SomeClass.prototype,
  "someMethod",
  SomeClass.prototype.someMethod
);

プロパティは上記の方法を使っても圧縮後の名前を固定できないので、getter/setter メソッドを用意し、そのメソッドに対してgoog.exportPropertyを使いましょう。

おわりに

Clutz というニッチなツールについて解説しました。kintone のコードベースは巨大でモダンスタックへの移行は一筋縄ではいきません。フロントエンドエキスパートチームでは、このようなツールを活用することでより効率よく、より安全に移行できないか日々探究しています。

今回のサンプルを応用し、型が保証された状態で安全に Closure Tools 側のコードを再利用できるのでは!と期待しています。

また、今回紹介した Clutz 以外に次のようなツールもあります。

  • Closure Library で記述されたコードを TypeScript のコードに変換するGents
  • 逆に TypeScript のコードを Closure のコードに変換するTsickle
  • Closure Library を TypeScript で利用可能にしたts-closure-library

これらについても探究していますので、なにか発見があったらまたブログを書きたいと思います。

脆弱性報奨金制度にチャレンジしてみよう

$
0
0

こんにちは、Cy-PSIRTの長友です。 今回は、「脆弱性報奨金制度に参加したいけれど一歩が踏み出せない」という皆さんに向けて、制度への参加方法と報告時のポイントをお伝えしようと思います。
この記事はあくまで弊社の考える始め方やポイントであって、他社の制度に報告される際はそれぞれのルールや報告方法を熟読したうえで報告されることをお勧めいたします。

脆弱性報奨金制度とは

セキュリティ上問題があると思われる挙動を見つけて報告をしていただき、脆弱性として認められるとその深刻度に応じて報奨金をお支払いする制度のことです。弊社では2016年度から毎年少しずつルールを変更し、実施しています。
この制度は、バグバウンティと呼ばれていることもあります。
ちなみに、バグを見つけてたくさんの報奨金を稼いでいるかたもいらっしゃるようです。(参考:サイボウズが自社製品のバグに懸賞金を出す理由って?──バグハンター合宿に密着取材してきた

脆弱性報奨金制度に参加するには

まずは、脆弱性報奨金制度を実施している企業や団体を探しましょう。
弊社は、こちらのページでご案内をしています。
以下のプラットフォームで探してみると、弊社以外にも様々な分野の企業・団体が脆弱性に報奨金を支払っていることが分かるかと思います。

興味を持った制度が見つかったらルールや注意事項、手続きのフロー、セキュリティに関連する法令などをよく読んで理解しておきましょう。
たとえば、弊社の脆弱性報奨金制度では、本番環境に影響を与えずに脆弱性を探していただくために検証環境提供制度を実施しています。こちらの環境を利用していただくことで、安心して脆弱性を探すことが可能となっています。

脆弱性を探してみよう

やってみたい報奨金制度を見つけ、ルールや注意事項の確認や必要な手続きが済んだらさっそく脆弱性を探してみましょう。
たとえば、実際にサービスを触ってみながら、挙動や通信の内容をじっくり観察してみると、なにか違和感や気になることが出てくるかもしれません。その気になる箇所に対してさまざまな攻撃手法を試してみるともしかしたら脆弱性が見つかるかもしれません。地道な作業ではありますが、ハマるととても楽しかったりもします(私も業務で弊社の製品に対して脆弱性検証をしていますが、リクエストを観察したり気になるところをつついていると時間を忘れて没頭してしまうことがよくあります)。
OWASP Cheat Sheet SeriesHackerOneで公開されている脆弱性のレポート、PortSwigger Researchなども参考になるかもしれません。また、過去に報告した脆弱性を公開している方々のブログなどもとても参考になるでしょう。

脆弱性を報告しよう

いざ脆弱性と思しき挙動が見つかったら、報告をしましょう。
ここでは、サイボウズにご報告いただく際のフォーマットについてご説明します。ある程度このフォーマットに沿ってご報告いただきますと、弊社としては迅速な対応が行いやすくなります(いただいているお問い合わせの件数にも左右されますので、必ずしもすぐに返答できるわけではないですが……)し、報告者の方も脆弱性の整理がしやすくなるかもしれません。また、報告者・受付側で脆弱性に対する認識の齟齬が出にくくなり、適切な評価にも繋がるのでは、と考えています。

1. 脆弱性が再現する手順と環境を整理しましょう

まずは発見した脆弱性を再現させるための手順と、再現できた環境をまとめましょう。
環境に関しては、使用したブラウザ、OSは最低限あるとよいですが、もしそれ以外に何かソフトウェアを使ったのであればそれらも列挙してください。
また、脆弱性を確認した製品名と動作環境(クラウド版/Windowsオンプレミス版/Linuxオンプレミス版)、製品のバージョン(分かる場合は)も記載してください。
手順は1つの操作を1つのステップにまとめて記載するとよいでしょう(読みづらい場合はいくつかの操作をひとまとめにしてもよいかもしれません)。

例:
ブラウザ:Internet Explorer
OS:Windows 10
製品名:ガルーン(クラウド版) 4/1 時点の最新版
① スケジュールの登録画面を開く
② タイトル欄に以下の文字列を入力する

'><script>alert(1);</script>

③「保存」ボタンをクリックする
④ 保存されたスケジュールを開くと、JavaScriptが動作する

また、リクエスト内の値を変更する必要がある場合は、変更後のリクエストとレスポンスそのものや、値を変更するパラメータ名を記載するとさらに分かりやすくなります。
「いやいやそんな細かく書かなくてもわかるでしょう」と思われるかもしれませんが、詳細に書いていただくと、受付側で問題点の把握がスムーズに行なわれたり、そのあとのコミュニケーションコストがぐっと下がったりする可能性が出てくるので、ぜひ詳細に書いていただけるとうれしいです。

2. なぜ脆弱性と判断したのか簡単にまとめましょう

操作だけでも十分といえば十分なのですが、これになぜ脆弱性と判断したかの理由を記載いただくとより分かりやすい報告になるのではと考えています。先ほどの例であれば「開発者の意図しないJavaScriptが挿入されるため、脆弱性と判断した」といったところでしょうか。これを記載することで、報告側・受付側で「何が問題になるか」の齟齬が発生しなくなることにより、納得感のある評価につながりやすくなると考えているからです。その挙動の何が問題なのかを端的に表現していただけるととてもありがたいです。手順などが込み入った報告になればなるほど、この説明が重要になってきます。

3. 報告フォームに記入しましょう

ここまでできれば記入に必要な内容はほぼ完成です。報告フォームに沿って必要事項の記入をお願いします。

おわりに

今回は、脆弱性報奨金制度を運用する側から見た脆弱性報奨金制度の参加方法とちょっとしたコツをお伝えしました。
制度に参加したいと思っていらっしゃるみなさんにご活用いただければ幸いです。

Cookie の SameSite 属性について

$
0
0

こんにちは、フロントエンドエキスパートチームの小林(@koba04)です。

フロントエンドエキスパートチームでは、日々の業務としてブラウザやライブラリの更新情報をキャッチアップして社内で共有しています。

例えば先日、CSSのプロパティである image-orientationのデフォルト値が noneから from-imageに変わったため、画像の Exif 情報の扱いが変更されました。

https://www.fxsitecompat.dev/ja/docs/2020/jpeg-images-are-now-rotated-by-default-according-to-exif-data/

注: Firefox では COVID-19 の影響により、この変更は延期されました。(Chrome は予定通り 81 で リリースしています)

https://blog.chromium.org/2020/02/chrome-81-near-field-communications.html

このような変更はプロダクトに影響があるため、フロントエンドエキスパートチームでは社内の kintone に専用のスレッドを作成し、変更点などを整理して伝えています。

f:id:cybozuinsideout:20200430182958p:plain
Browser backward compatiblity / ブラウザ後方互換性情報

上記は実際の投稿の一部であり、以降に変更の詳細などを記載して取るべき対応について記載しています。 これらの情報の多くは社外にも公開可能なため、今回は Cookie の SameSite 属性について書きます。

今回確認するために使用したブラウザのバージョンはそれぞれ下記の通りです。

  • Chrome 81
  • Firefox 75
  • Safari 13.1

SameSite 属性とは

Cookie に指定可能な新しい属性です。 Chrome, Firefox, Safari の全ブラウザですでに実装されています。仕様は現在 Internet Draft の状態です。

https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-06

SameSite 属性には、次の値が指定できます。

None

これまでの Cookie の挙動通り、全ての same-site なリクエストに対して Cookie が付与されます。

Strict

same-site に対するリクエストにのみ Cookie が付与されます。Cookie を使いログイン認証をしているサイトに対して cross-site なサイトに設置されたリンクから遷移した場合、リクエストに Cookie が付与されないため未ログイン状態になります。これは CSRF の攻撃に対しても有効ですが、ユーザーは cross-site なサイトからログインした状態で遷移できないため、ユーザーは再度ログイン処理や再読み込みが必要となります。

Lax

same-site に対するトップレベルのナビゲーションに対してのみ Cookie が付与されます。クロスサイトのスクリプトや画像などのサブリソースに対するリクエストには Cookie が付与されません。Laxの場合、Strictで解説したケースでは Cookie が付与されるため、ログインした状態で遷移できます。ただし、POST メソッドのような unsafe な HTTP メソッドによる cross-site なトップレベルのナビゲーションに対して Cookie が付与されません。したがって、POST メソッドなどを使ったフォームのサブミットに対する CSRF の攻撃に対しても有効です。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies

上記で言及した same-site や cross-site については、下記の記事がとてもわかりやすいのでオススメです。

https://web.dev/same-site-same-origin/

SameSite 属性のデフォルト値を Lax にする流れ

Chrome や Firefox では、この SameSite 属性のデフォルト値を None ではなく Lax に変更することを計画しています。 Chrome ではすでに SameSite 属性のデフォルト値を Lax にする変更を Chrome 80 から段階的にロールアウトしていました。 ただ、COVID-19 の影響を鑑みてロールアウトを一旦中止することを発表しています。

https://blog.chromium.org/2020/04/temporarily-rolling-back-samesite.html

Chrome の最新の状況については下記ページで確認できます。

https://www.chromium.org/updates/same-site

また、Firefox でも同様に Lax をするデフォルト値にするための作業が進められています。

https://bugzilla.mozilla.org/show_bug.cgi?id=1617609

Safari での SameSite 属性が None である Cookie の扱い

一方 Safari は、2020 年 3 月に 3rd Party Cookie を完全にブロックするとアナウンスしています。

https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/

この挙動は SameSite 属性に None を指定した時の挙動と矛盾するため、Safari だとどうなるのだろうと思い調べてみたのが今回のきっかけです。

Safari 13.0 及び 13.1 で動作確認しましたが、3rd Party Cookie をブロックするケースとしないケースがあることがわかりました。 さらに挙動を調べてみたところ、Website tracking の設定である Prevent cross-site tracking の設定によって挙動が変わることがわかりました。

f:id:cybozuinsideout:20200430175409p:plain
SafariのWebsite Trackingの設定

この設定が有効になっている場合、SameSite 属性に None を指定しても 3rd Party Cookie は付与されません。 この変更がいつから適用されているのかは不明ですが、現在ではこの設定がデフォルトで有効になっているため、Safari ではデフォルトで 3rd Party Cookie が付与されない状態になっています。

ただし、Storage Access API を利用することで 3rd Party Cookie を利用可能にする方法が用意されています。

https://webkit.org/blog/8124/introducing-storage-access-api/

SameSite 属性を DevTools で確認する

今回この検証を行っている際、SameSite 属性が None の場合の各ブラウザの DevTools での表示が異なっていることがわかりました。 下記は、それぞれのブラウザ毎に取得したスクリーンショットです。

  • Chrome

    f:id:cybozuinsideout:20200430175439p:plain
    Chrome 81でのCookieの表示

  • Firefox

    f:id:cybozuinsideout:20200430175501p:plain
    Firefox 75でのCookieの表示

  • Safari

    f:id:cybozuinsideout:20200430175516p:plain
    Safari 13.1でのCookieの表示

Chrome は未指定と None が区別されています。したがって、現状だとデフォルト値が None なのか Lax なのかを理解する必要がありそうです。 Firefox はデフォルト値も含めて全て None と表示されています。 Safari は None の表示は — で表示されます。バグかと思い確認してみましたが意図した挙動のようです。

参考:

まとめ

今回は、Cookie に新しく追加された SameSite 属性について調べた内容を共有しました。

このようにブラウザの変更をキャッチアップして、プロダクト開発チームにフィードバックするのもフロントエンドエキスパートチームの仕事です。 サイボウズでは、ブラウザの更新情報をチェックするのが趣味というエンジニアを募集しています。

https://cybozu.co.jp/company/job/recruitment/list/front_end_expert.html

Viewing all 698 articles
Browse latest View live