Cybozu Inside Out | サイボウズエンジニアのブログ

 

npmとyarnの脆弱性とpostinstall

フロントエンドエキスパートチームの小林(@koba04)です。

先日、npmから脆弱性についての発表がありました。 調べていく中でいくつか思うところがあったので解説も兼ねて書いていきたいと思います。

The npm Blog — Binary Planting with the npm CLI

npmの利用者としてやるべきことは、

  • npmのバージョンを6.13.4以上にあげる
  • yarnのバージョンを1.21.1以上にあげる

です。 npmのバージョンが6.13.4になったNodeもv8, v10, v12, v13系でそれぞれリリースされたので、そちらを利用することも可能です (yarnのバージョンは別途あげる必要があります)。

nodejs.org

npmによる発表では、今回発表された脆弱性は2件あるため、それぞれ個別に考えます。

binに任意のパスを指定出来る件

npmパッケージはpackage.jsonbinフィールドに、インストールすることで使えるようになるコマンドを指定出来ます。 例えば、eslintの場合は下記のように指定されているため、インストールするとeslintコマンドがnpm-scriptsやnpx経由で利用可能になります。

github.com

パッケージをグローバルにインストールしない場合、このコマンドはnode_modules/.binに配置されます。 npm-scriptsの実行時はここにPATHが通っているため、npm-scriptsではeslintコマンドとして実行できます。

今回は脆弱性の話なので詳しく解説しませんが、binに興味持った人は下記のドキュメントを参照してください。

docs.npmjs.com

今回の脆弱性はここのコマンド名の部分に、../../cdなどと指定したら…?という話です。 現状では特にコマンド名に対するチェックが行われていなかったため、インストールするだけで任意のファイルを置き換えることが可能であり、これが脆弱性であると判定されました。

npmでは、この辺りのcommitで修正されています(内部で利用している別パッケージの修正)

github.com

yarnでは、この辺りのcommitで修正されています

github.com

この脆弱性は、孫依存のような開発者自身が明示的に指定せずにインストールされる依存パッケージからでも悪用可能なため、対応すべきです。

グローバルにインストールしたパッケージが別のコマンドを上書きできる件

npmはプロジェクトローカルにパッケージをインストールするだけでなく--global(-g)フラグを使うことでグローバルにパッケージをインストールできます。 yarnの場合は、globalコマンドを利用します。

グローバルにパッケージをインストールした場合、パッケージが提供するコマンドのシンボリックリンクが/usr/local/bin以下に作成されます(作成される場所は実行環境により異なります)。 /usr/local/binといえば、他にもHomebrewなどでインストールしたパッケージのコマンドも配置される場所です。

この脆弱性は、グローバルにインストールされたパッケージが、binに指定しているコマンドによってすでにある別のコマンドを上書きできるというものです。 例えばjqをHomebrewか何かでインストールしている状態で、悪意のあるnpmパッケージをグローバルにインストールした場合、そのbinフィールドに"jq" : "evil.js"のように書かれていると、jqコマンドが上書きされてしまうということです。

グローバルにインストールすること自体がそんなに多くないのと、よくわからないnpmパッケージをグローバルにインストールすることはほとんどないと思うので、1つめのものに比べると深刻度は低いかなと思います。

npmでは、この辺りのcommitで修正しています。

github.com

対応方法としては、すでにコマンドが存在する場合、シンボリックリンク先が今からインストールしようとしているものと同じかどうか確認しています。

yarnでは、これに対する修正はまだ行われていません。 なので、下記のissueでどうするのか聞いてみました。

github.com

上記のIssueによると、yarnでは今回の件を下記の3つに分けて考えています。

  • Binary planting
  • Out-of-tree execution
  • Binary overlap

Binary plantingは1つ目に紹介した脆弱性で、前述した通りyarnは1.21.1で修正済みです。

Out-of-tree executionは、binフィールドのコマンド名の部分ではなく、ファイルパスを指定する側に/home/foo/bar のように指定することで任意のポイントに対するシンボリックリンクを作成できるというものです。

    {
      "bin": {
        "foo": "/home/foo/bar"
      }
    }

これについては、Binary plantingの対応と同時に対応されています。 ただしこちらに関しては、脆弱性ではなくbugfixという扱いにしています。

Binary overlapは2つ目に紹介した件であり、yarnでは対応されていないものです。 対応しない理由としては、上記のIssueでは下記のように説明されています。

  • 過去にBinary overlapを意図的にやっていたパッケージがあり、その挙動に対してBreaking Changeとなるため
  • これらのパッケージは、ユーザーが意図してインストールしたパッケージであるため

そのため、特にグローバルなパッケージをインストールする場合には、注意深く行う必要があります。

上記のIssueによると、yarnはv2でbinスクリプトはNodeを通じてのみ実行可能にすることを計画しているようです。 これにより、Node上でサンドボックス環境を構築してその上で実行させることが可能にできる可能性があります。 yarnはNodeがこのようなセキュリティポリシーを実装することを期待しています。

postinstallとinstall

今回の脆弱性についてはここまでですが、前述したIssueではpostinstallの危険性についても言及されています。

npmでは、publishやpackなど、任意のタイミングで呼ばれるscriptをnpm-scriptsとして登録出来る仕組みがあります。 どのようなscriptを登録出来るのかは下記のページより確認可能です。

docs.npmjs.com

その中に、installpostinstallがあります。 これらは利用者がパッケージをインストールする際に実行されるスクリプトです。 つまり、ただnpm installするだけでインストールしたパッケージに対して任意のスクリプト実行を許可していることになります。 そのため、結果的に悪意のあるパッケージが依存関係に含まれてしまった場合には、npm installするだけで悪意のあるスクリプトを実行されてしまいます。

つまり、今回のような脆弱性を利用せずとも悪意のあるパッケージはうまく依存関係に含まれることに成功すれば任意のスクリプトをユーザー環境で実行できます。 …。

そのための対策として、npm-scriptsの実行をしないための--ignore-scriptsというオプションがあります。

docs.npmjs.com

これを利用することでインストール時に任意のスクリプトの実行を拒否することが可能です。 ただ、これを使うためにはなぜinstallpostinstallが使われているのかを理解する必要があります。 現状、下記の2点の用途で利用されていることが多いと思います。

  1. ユーザー環境に応じた処理
    • C, C++などのコードを含んだネイティブモジュールのビルド
    • 環境に応じたバイナリのダウンロード
  2. Fundingのお願い

1のケースで言えば、fseventspuppeteerなどがよく目にケースではないでしょうか。 ただ、fseventsに関しては下記のPRでinstallスクリプトを実行しないようになっているので、最新版だとインストールスクリプトは実行されません。

github.com

puppeteerでは下記のスクリプトでchromiumのバイナリをダウンロードします。

github.com

2.のケースは、最近npm installnpm ciの際に大量のfundingのお願いのメッセージを目にすることがあるのではないでしょうか? これは、多くの場合postinstallを使ってconsole.logでメッセージを出力することで実現されています。

ではこれらをなくすことが出来るのかということですが、まず2のケースについては言えば、npm v6.13からサポートされているfundingのフィールドを使うことで、npm fundコマンドで出力することができます。

The npm Blog — Updates to Community, Docs & more...

npm installでパッケージをインストールした時にnpm fundに対応したパッケージが依存にあると、下記のような形で出力されます。

:
added 15 packages from 17 contributors and audited 15 packages in 1.574s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

npm fundを実行すると下記のような形で出力されます。

% npm fund
test@1.0.0
└─┬ fetch-mock@8.2.0-beta.2
  ├── type: charity
  ├── url: https://www.justgiving.com/refugee-support-europe
  └─┬ core-js@3.6.0
    ├── type: opencollective
    └── url: https://opencollective.com/core-js

今回の脆弱性により、多くのユーザーはnpm 6.13.4以上を利用するようになるため、npm fundコマンドが利用可能になります。 そのため、postinstallではなくnpm fundを使うための準備が整ったとも言えます。

yarnは現在fundコマンドをサポートしていませんが、前述したIssueによるとv2には実装してバックポートすることが検討されているようです。

1のケースについては、パフォーマンス目的でC, C++で書いている場合には、WebAssemblyで置き換えることが可能かもしれません。 ただ、NodeがサポートしていないネイティブのAPIを使いたい場合には、現時点ではまだ使い続ける必要がありそうです。 将来的にはこれについてもWebAssemblyで解決出来る可能性はあるかもしれませんが。

そもそもの現状として、どんなスクリプトが自分のプロジェクトのnpm install時に実行されているのか把握している人は少ないのではないでしょうか? インストール済みのパッケージでinstallまたはpostinstallのスクリプトがあるものを出力するためのnpm packageを作ってみたので興味ある人は自身のプロジェクトで実行してみてください。

% npx install-scripts

github.com

こちらは社内のプロジェクトで実行した結果です。 この中で動作に必要なscriptはfseventspuppeteerだけでした。他は全てfunding関連です。

    core-js
      scripts:
        postinstall: node scripts/postinstall || echo "ignore"
      paths:
        node_modules/@babel/polyfill/node_modules/core-js/package.json
        node_modules/@storybook/addon-actions/node_modules/core-js/package.json
        node_modules/@storybook/addon-knobs/node_modules/core-js/package.json
        node_modules/@storybook/addon-links/node_modules/core-js/package.json
        node_modules/@storybook/addon-viewport/node_modules/core-js/package.json
        node_modules/@storybook/addons/node_modules/core-js/package.json
        node_modules/@storybook/api/node_modules/core-js/package.json
        node_modules/@storybook/channel-postmessage/node_modules/core-js/package.json
        node_modules/@storybook/channels/node_modules/core-js/package.json
        node_modules/@storybook/client-api/node_modules/core-js/package.json
        node_modules/@storybook/client-logger/node_modules/core-js/package.json
        node_modules/@storybook/components/node_modules/core-js/package.json
        node_modules/@storybook/core/node_modules/core-js/package.json
        node_modules/@storybook/core-events/node_modules/core-js/package.json
        node_modules/@storybook/node-logger/node_modules/core-js/package.json
        node_modules/@storybook/react/node_modules/core-js/package.json
        node_modules/@storybook/router/node_modules/core-js/package.json
        node_modules/@storybook/theming/node_modules/core-js/package.json
        node_modules/@storybook/ui/node_modules/core-js/package.json
        node_modules/fetch-mock/node_modules/core-js/package.json
        node_modules/lazy-universal-dotenv/node_modules/core-js/package.json
        node_modules/simplebar/node_modules/core-js/package.json
        node_modules/wait-on/node_modules/core-js/package.json
    core-js-pure
      scripts:
        postinstall: node scripts/postinstall || echo "ignore"
      paths:
        node_modules/core-js-pure/package.json
    fetch-mock
      scripts:
        postinstall: node scripts/support-fetch-mock.js
      paths:
        node_modules/fetch-mock/package.json
    fsevents
      scripts:
        install: node install
      paths:
        node_modules/fsevents/package.json
    puppeteer
      scripts:
        install: node install.js
      paths:
        node_modules/puppeteer/package.json
    styled-components
      scripts:
        postinstall: node ./scripts/postinstall.js || exit 0
      paths:
        node_modules/styled-components/package.json
  • fetch-mockは最新版ではすでにfundを使うようになっています
  • core-jsについてはこちらで議論されていますが、今のところ削除する予定はなさそうです
  • styled-componentsについては過去に議論されたIssueがなかったので確認してみました

下記のように組み合わせることで必要なスクリプトのみを実行することも可能です。

    % npm ci --ignore-scripts
    % npx install-scripts

yarnはpostinstallについて、既存のエコシステムを壊してしまうのでv2で全て無効にすることは難しいですが、v3では考えているようです。 ただし、現時点でも手動でホワイトリスト管理によって許可されたscriptのみ実行可能にすることは可能だとしています。

実際にpostinstallinstallを調べてみた結果としては、そこまで多くないなという感想だったので--ignore-scriptsを使った運用するのも可能であるように感じました。 そのためにも、今度はpackageをホワイトリストで指定して実行できるnpm packageを作ってみようかなと思いました。 ただこれ自体はnpm自体が対応すべき問題であると感じるので、Feature Requestを送りました。

github.com

以前には下記のevent-streamに対する問題がありましたが、現状ではインストールするパッケージを全て把握することは現実的ではありません。

The npm Blog — Details about the event-stream incident

今回の脆弱性を通じて、npmの現状や将来の方向性などを考える機会になればいいなと思ってこの記事を書きました。


サイボウズでは、脆弱性があった時にcommit log読んで原因を探求したくなるエンジニアを募集しています!

cybozu.co.jp

 

コミュニケーターとしての初日本出張

こんにちは!NGUYEN XUAN TRA(チャ)とTRAN THI THAM(タム)です。 ベトナム拠点のコミュニケーターチームに所属しているメンバーです。 今回コミュニケーターとして4週間日本に出張したので、そこで体験したことや感じたことを報告します。

ベトナム拠点とコミュニケーターについての紹介

本題に入る前に、サイボウズのベトナム拠点と、コミュニケーターという仕事について簡単に紹介します。

ベトナム拠点はベトナムのホーチミン市に置かれています。 blog.cybozu.io

ベトナム拠点はガルーンの開発を中心に、kintoneカスタマイズもしています。 私たちコミュニケーターの主な業務は、日本側メンバーとベトナム側メンバーの間に入って、通訳・翻訳することです。 ベトナムの開発チームでは日本語ができる人はそんなに多くはいないので、このようなコミュニケーターのチームが生まれました。

コミュニケーターは、普段の仕事でいつもベトナムオフィスを走り回りながら、テレビ会議を通じて、通訳するのが多いです。日本人メンバーとの直接のやり取りはほとんどありません。 そのため、日本メンバーとの関係をもっと深めることを目的として今回の出張を計画しました。 さらに、この機会にコミュニケーターの今後のキャリアのため通訳以外の勉強をすることも同時に計画に含めて、いつもの一週間ぐらいの出張よりちょっと長めに滞在することになりました。

業務体験

新設された翻訳チームの今後の見通しを学ぶ

最近、サイボウズでは「翻訳チーム」という新しいチームが設立されました。 このチームは、社員がWebサイト・資料などの翻訳で困ったときに依頼・相談してもらえることを目指しています。チームによる調査の結果、社員からの翻訳の依頼がかなり上がってきていることが分かっています。 翻訳チームは、それらの依頼について社内で対応することと外注することを考えていますが、所属しているメンバーがほとんど兼務なので、リソースが不充分な状態です。 社内対応の場合、私たちにも対応できる依頼がありそうです。現時点では、どのような形で貢献できるか、また工数が足りるかどうかもわからないのですが、コミュニケーターとしての翻訳経験を生かして少しでも力を尽くしたいと思うので、翻訳チームの状況については今後も情報収集していきます。

グローバルのコミュニケーションの要望について

サイボウズでは一つの部署やチームでも複数の拠点を跨いで、一緒に仕事をやることがあります。拠点は日本国外(アメリカ、中国、ベトナムなど)にもありますが、日本の本社からの情報はほとんどが日本語で共有されているので、拠点間でコミュニケーションをとるときには多少困っているメンバーもいます。

このため、どこの拠点にいても全員に平等に情報(戦略会議の議事録とか)を共有できるように、英語化が進められつつあります。

私たちは今回の出張で日本に来たタイミングで、これに関して人事担当者から話を聞かれました。コミュニケーターの立場から見て、他拠点とやり取りするときに共通の言語があった方がいいと思うかという話です。 チャとタムはそれぞれに違う意見を言いました。

チャは日本語が大好きで、日本語の勉強に集中していたので、英語より日本語の方が得意です。なので、共通の言語を英語にするのはあまり嬉しくないと思いました。英語は母語ではなく、伝えたいことを完全に伝えきれないので、話がややこしくなるという懸念点もあります。

文章だと、母語ではなくても伝えたいことをじっくり考えながら書くし、分からなくても調べながら読むことができるので、だいたいの意味が伝わります。それに対して、口頭で英語を話すと、ベトナム人と日本人の発音の癖に違いがあり、お互いに聞き取れなくて苦戦してしまうときもあるので、やっぱり日本語で話したいと思います。

逆にタムは、みんなが共通の言語として英語で話すようになるといいなぁ~と思います。言葉は通訳すれば伝わりますが、言葉の他にも伝えたい、その瞬間の気持ちがあるからです。例えば、日本・上海・ベトナムの3拠点で会議するときは日本語とベトナム語の通訳をします。上海には日本語が上手なメンバーが多いので、日本語がわからないベトナムだけ、拍手のタイミングや、ジョークが伝わるタイミングがずれてしまうこともあります。

英語は誰の母語でもないですが、共通言語にすれば話しているタイミングで何となくわかるので、反応がずれてしまうこともなくなるかもしれません。

感動課についての説明

感動課とは何の課でしょう? 「サイボウズに関わる人に、関わって良かったなと思ってもらう」というミッションで活動している課です。 私たちはサイボウズに入る前にもベトナムにある他の日系企業で働いたことがありますが、そういう部署があることはサイボウズに入ってから初めて知りました。たぶんこういう課はサイボウズだけにしか存在しないと思っています。

今まで主に活動しているのは日本だけです。ベトナム拠点でも感動課に相当するチームをこれから設立する予定がありますので、日本の担当者から色々経験を共有してもらいました。ベトナムにもそのようなミッションを担っているチームがあったら、社内にもっと感動があふれてくるでしょう?

スクラムについての研修

サイボウズのスクラム支援の担当者からスクラムの概要を説明してもらいました。 簡単なワークショップにも参加しました。

ワークショップを通じて、定義の曖昧な言葉(確約 (commitment)・勇気 (courage)・集中 (focus)・公開 (openness)・尊敬 (respect) 等)が具体的に何を指しているのか分かるようになりました。

5価値に関するワークショップ
スクラムの5つ価値基準の良い・悪い例をあげるワークショップ

スクラムマスター (SM) は、開発チームの作業を妨げる障壁をなくしたり、問題を解決したりするための方法を考えます。SMの主な仕事は「思考」です。SMは仕事としてしょっちゅう「思考」するのですが、それを具体的な行動としては表現できないから、周りの人に理解してもらえず、「あの人は何もしていないな~」と思われてしまうことが多いですね。

たぶんそれゆえに、ベトナム拠点ではSMの役割を担当している人が他の役割(QA, PG等)を兼務しないと不安になってしまうようです。

日本側でSMを専任としてやっている人の姿を見て素晴らしいと感じたので、ベトナム側のみんなにはQA, PGと同様に、SMも専任でやるべきことだと理解してほしいです。そうすれば、SMの役割が発達し、SMならではの改善もでき、会社によりよい貢献ができるでしょう。

PBIとSBLの違い
おまけ:Product Backlog Items (PBI) と Sprint Backlog (SBL) の違いの説明

ガルーンの体験入部

日本側のガルーンの開発スクラムチームであるJupiterに参加しました。 Jupiterチームは体験入部する期間に、休暇を取ったり、別の予定があったりして、皆さんも忙しいのに、私たちに暖かい気持ちで色々に教えていただいたので、とても感謝しています。 Jupiterチームで朝会やQAのモブ会に参加しました。両方ともWEB会議ソフトZoomという体制で開催しました。Zoom なら、リンクがあれば、自席などどこからでも参加できるので、距離感も縮まりますし、別の拠点の人も気軽に参加して一緒に作業できるというメリットがあります。その一方で、一日中ヘッドフォンを着けているので耳が痛くなるのがデメリットの一つです。もう一つのデメリットは、自席からヘッドフォンで話すため、隣の人の声が漏れて集中できなくなってしまったりすることです。ちなみに、一番感心したのはタスクの実施に透明性があることです。やっている作業をコメントで常に共有することで、周りの人がいちいち聞かなくても作業がどこまで進んだのか把握できるのでとてもいいと思います。

また、普段の仕事ばかりしていると交流する時間があまりないので、毎日の午後に1時間ぐらい話すための場を確保していただきました。話した話題は、SMの役割など真剣な内容から、デスマーチという新しい用語、PGが集中すると休憩さえ忘れてしまうことなど気楽な話までいろいろありました。

研修期間の第4週目の最後の2日間は、Jupiterの事情でSiriusチームに参加することになりました。Siriusチームとは製品に関するお客様からの問い合わせを受けて、対応するチームです。それで、問い合わせの調査にも参加させていただきました。この仕事の難しさは、相手の意図を汲むことはもちろん、相手がまだ考慮していない部分まで考慮して、提案したり、確認したりする能力を身に着ける必要があることだと分かりました。そして、文章を書くとき、必ず相手に伝えたいことを頭の中で整理してから、相手が分かりやすい易しい文章を書くことがとても大事だと気づきました。

その他

部活動の楽しさ

業務以外の活動にも参加しました。

ジムに一緒に行って、TRXとスパルタンワークアウトをやってみました。ジムに滅多に行かない私には具体的に何のトレーニングなのかも最初は分からなかったのですが、実際に参加してみると、どうやらTRXトレーニングは紐を使い、体をストレッチして、筋肉、柔軟性、バランス力を総合的に鍛えるトレーニングです。そして、スパルタンは走ったり、ジャンプしたりする激しい運動の間に短い休憩を入れ、それを繰り返すトレーニングでした。

結構長く真面目に運動していない体なので、本気で使いこなすと辛かったです。面白いことは体が疲れれば疲れるほど頭がすっきりになることですね。パソコンの前に座り込みすぎると、頭の動きも鈍くなるわけですね。今回をきっかけにして、これからも引き続き体を鍛えようとやる気が湧いてきましたよ。

スパルタンワークアウト初参加
疲れ切ったけど、皆頑張ってスマイルしている!

サイボウズには他にもいろいろ楽しそうな部活動があります。例えば、甘い物を食べながら交流するスイーツ部や、月に1回くらいみんなで集まって一緒に料理をする料理部等です。今回はその料理部に参加し、みんなと料理を作らせてもらいました。

その日のメニューはキノコの炊き込みご飯、ベビーリーフのサラダと豚肉の生姜焼きでした。

料理部で作った料理
出来上がった成果でしゅよ~🤤

定食屋さんに行けば、同じメニューを簡単に注文することができるかもしれませんが、みんなと一緒に準備してから食べる料理にはどの店でも味わえない親しみやすい家庭の味がしていて、胸がジーンとしました。

料理部のみんなでご飯を食べる
会社だって大きな家族ですね

まとめ

一ヶ月は矢のようにあっという間に過ぎました。今回の出張のお陰で、普段仕事であまり接するチャンスがないメンバーとも話し合うことができました。

通訳者といっても、ベトナムで長く生活しすぎると、日本人など外国人と話すときに多少緊張してしまう気がします。コミュニケーターはベトナムと日本とのコミュニケーションの橋になる存在なので、緊張という気持ちは通訳の障害になると言っても過言ではないでしょう。こういう風に日本に出張して行って、日本のメンバーと直接触れ合う回数を増やすことにより、自信を持つようになり、緊張する気持ちを多少制御できるようになれると思うので、とてもありがたく感じています。

また、ベトナムの開発チームが日本に出張することになったら、日本オフィスでサポートするのは基本的には日本メンバーです。しかし、日本メンバーも忙しいでしょうから、コミュニケーターがこういう出張で日本に馴染むようになって、サポートを手伝えるようになるとよいかもしれません。

最後に、出張と言えば大変な印象があるかもしれないんですけど、また機会があれば前向きな態度で出張に行きたいです。そして、もっと良いコミュニケーターになり、毎日元気で、新しい自分にチャレンジし続けたいと思います。

 

ストレージオーケストレーターRookへのサイボウズのコミット方針

はじめに

こんにちは、Necoプロジェクトのsatです。

NecoではKubernetes上のアプリケーションが使うストレージをCephによって提供すること、およびCephクラスタの管理にKubernetes上で動作するストレージオーケストレーターRookを使うことを決めています。本記事はNecoがなぜRook/Cephを選択したのか、Rookに対する現在のコミット状況、および今後のコミット方針について紹介します。

CephやRookがどういうものなのかについては以下の記事をごらんください。

blog.cybozu.io

blog.cybozu.io

なぜRook/Cephなのか

まずはNecoがなぜストレージをRook/Cephによって提供することにしたのかを簡単に書いておきます。

Ceph

Necoで提供するストレージには、既存のインフラで苦労した点を無くす、あるいは軽減するために次のような要件を定義しました。

  • OSSである: トラブル発生時に自分たちの手によって早急に問題を解決したい。
  • コミュニティが活発である: バグの検出や修正、機能開発に必要な人的資源が多いほどよい(後述のようにサイボウズもその一員となる)
  • スケーラビリティが高い: サーバ一台に搭載できる程度の容量にとどまらず、数PB単位のデータを保持したい。
  • 高可用性: ハードウェア障害の発生時やハードウェアメンテナンス時にサービスを継続したい。
  • セルフヒーリング: デバイスが壊れてもオペレータの介在なしにデータの冗長性を回復したい。
  • bit-rot耐性: ストレージデバイスの障害によってデータが壊れても自動検出/復旧可能したい。

様々な候補の中から上記の要件を満たすものを調査したところ、Cephが適しているという結論に至りました。

Rook

Cephの機能は豊富なのですが、いざ運用するとなるときには一つ大きな課題があります。それはノードの追加/削除を含めた管理が難しいためにオペレータへの負担が大きいということです。この課題を解決する既存の方法があるかを調査したところ、Kubernetesを活用してCephクラスタを宣言的に管理するRookが候補にあがりました。

幸いにもRookは必要な機能の多くが既に実装されており、かつ、コミュニティも活発であったことより採用することになりました。

これまでのRookコミュニティへの貢献

前節において述べたようにRookはNecoに必要な機能の多くを実装しているのですが、いくつか不足している機能もあります。これらの不足機能についてはRookの修正を自社内で済ませるのではなく、弊社のオープンソースソフトウェアポリシーに従ってアップストリームの Rook にPull Requestを送ることにしました。 blog.cybozu.io

では具体的にどのような機能を追加する予定なのかを簡単に紹介いたします。

デバイスの指定にudevがつけたpersistent device nameを使う

現在RookがCephのOSDに使用するデバイスは/dev直下のものだけを指定できます。しかしこれには大きな落とし穴があります。

たとえばノードに/dev/sda,/dev/sdb,および/dev/sdcという3つのデバイスが存在しており、かつ、/dev/sdbをOSDとして使いたい場合を考えると、次のような問題が発生します。

  • /dev/sdbが故障した状態でノードを再起動すると/dev/sdcは/dev/sdbとして認識される
  • デバイスが故障していなくてもハードウェアの設定変更やストレージデバイスの挿入位置などによってデバイスの認識順序が変わる

f:id:cybozuinsideout:20191203012740j:plain

つまり/dev直下に表示されるデバイス名はカーネルがデバイスを認識した順に適当につけたものであって、ハードウェアの構成変更によって変化しうる信用ならないものであるということです。

この問題を解決するためにLinuxにはudevというデバイス管理ツールが存在します。udevを使えば各デバイスには例えばどのバス上にどのスロットに挿入されているかという位置情報をもとにした名前を付けられます。この位置情報はノードごとに一意であるため*1、このようなデバイス名はpersistent device nameと呼ばれます。

我々はこの問題をすでにissueとして報告しており、かつ、この問題を修正するためのPull Requestも発行しました。現在は実装方針についてメンテナと議論中です。

github.com

github.com

OSD on PVC機能においてLogical Volumeをサポートする

Rookは前節において述べたようにCephのOSDに使うデバイスとしてノード上のデバイス名を指定できます。これに加えてRook 1.1からは、KubernetesのPVCによって作成されるブロックボリューム上にOSDを作成できるようになりました。これによってユーザはノード上のデバイス構成を意識する必要が無くなったため、Rookの管理がさらに簡単になりました。

その一方でNecoはTopoLVMというCSIドライバを自作することによって、ノード上のNVMe SSDをPVCとして用途別に切り出せるようにしています。

blog.cybozu.io

ここでTopoLVMとOSD on PVCを組み合わせると、LVMから切り出したLogical VolumeをPVCという形でCephに提供できるというわけです。

f:id:cybozuinsideout:20191203012841j:plain

ただし現状のOSD on PVCは、PVCによって提供されるデバイスがLogical Volumeである場合はうまく動かないという問題があります。この問題についてもissueおよびPull Requestを発行してマージに向けて活動中です。

github.com

github.com

OSDの分散配置

OSD on PVCにはLogical Volumeサポートとは別に、致命的とも言える大きな問題があります。それは、耐障害性を最大限に高めるためにはOSDはすべてのノードに可能な限り均等に分散配置する必要があるものの、現状ではそれが困難であるというものです。

まず前提として、Rookは1つのOSDに対して1つのPodを動作させます。通常Podはどのノード上で動くかが不定なので、極端なことをいうと全OSD Podが同じノードに配置される可能性もあります。OSD用のPodがどのノード上で動かすかは一応nodeAffinity、podAffinity、podAntiAffinity、およびtolerationsを使って制御できるのですが、これらの方法では1つのノード上に1つのOSD Podしか置けないのです(詳細は以下リンクのコメントを参照)。

github.com

この問題を解決するのがKubernetes 1.16においてalphaになったPod Topology Spread Constraintsという機能です。この機能は、簡単に言うと特定のラベルが付与された沢山のPodをノード間やラック間で均等に分散配置するためのものです。この機能をOSD Podに適用すると、上記の要件が達成できるというわけです。

f:id:cybozuinsideout:20191203012915j:plain

すでにRookにおいてPod Topology Spread Constraintsをサポートするためのfeature requestを発行しており、現在は実装方針についてメンテナと議論中です。

github.com

Pod Topology Spread Constraintsについての詳細はKubernetesの公式ドキュメントやチェシャ猫さんのドキュメントをごらんください。

kubernetes.io

speakerdeck.com

Rook/Cephコミュニティへのコミット方針

先日開催されたKubeCon North America 2019において、NecoプロジェクトのメンバはKubernetes関連のメンテナと対話できる"Meet the Maintainer"という機会を利用して、Rookのメンテナと会ってきました。

events19.linuxfoundation.org

kccncna19.sched.com

このときに我々(Neco)は自社(サイボウズ)の次期インフラにおいてRook/Cephを使うことにしたこと、および、次期インフラが存在する限りアップストリームのRookに機能追加やバグ修正をし続けることを伝えました。これに加えて、我々が直接必要なもの以外でも、ユーザ対応やPull Requestのレビューなどにおいて可能な限り協力することも伝えました*2。Rookコミュニティには開発者の人的資源が不足しているらしく、我々の申し出は非常に歓迎されました。

今後はメンテナに表明したことを有言実行すべく、引き続きRookコミュニティのよき一員として、機能追加やバグ修正などの貢献をしていく予定です。RookはNecoの中において非常に大事なコンポーネントの一つであるため、Necoのメンバからメンテナを出すくらいの勢いで活動いたします。

おわりに

最後になりますが、Necoでは本記事で述べたような活動に興味のあるかたを絶賛募集中です。ご興味のあるかたは是非応募していただきたいと思います。

cybozu.co.jp

もちろんそれ以外の業務に興味のあるかたもお待ちしています。

*1:正確には一意に定まらないケースもあるのですが、ここでは割愛します

*2:すでにgithubのissueやRookのslackを介していくつかユーザサポートをしています

 

フロントエンドカンファレンス福岡 2019 に行ってきました

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

先日、11/16(土)に福岡の九州産業大学で行われたフロントエンドカンファレンス福岡 2019 にシルバースポンサーとして協賛しました。チームから@toshi-toma@__sakito__が登壇し、当日はチームで参加したので、その時の様子をお伝えしたいと思います。

https://frontend-conf.fukuoka.jp/

登壇について

まず、登壇した@toshi-toma@__sakito__の登壇内容について、紹介します。

@sakito

セッション概要

speakerdeck.com

なぜテストを書くのかという説明。テストの開発コストと速度のバランスを考えて、testing trophy で提唱されている Static,Unit,Integration,End to End,の 4 層の解説。

React,React Hooks のテストについてどのようにテストを書いていくのかという話をしました。

@toshi-toma

セッション概要

speakerdeck.com

フロントエンドエキスパートチームとして複数のプロダクトに関わった経験を活かし、これまでのフロントエンドの遍歴について話すことで、いまのフロントエンドがどのような状態なのかについて話しました。内容としては、UI ライブラリ、モジュール、AST と周辺ツールについて深ぼって話しました。

各メンバーの感想

次に、参加したメンバーの感想を一言ずつ紹介します。

  • @koba04
    • 昨年以上の盛り上がりを感じました。これからも福岡盛り上げていきたいと思いました!
  • @__sakito__
    • 去年も参加させて頂いていましたが、ノベルティ、会場 はもちろん細かい部分のクオリティがとても高くなっており、スタッフの方の福岡を盛り上げようという気持ちを感じました!
  • @shisama_
    • 福岡の盛り上がりを感じられてよかったです。セッションのカテゴリーも多く、様々な学びがありました。多くの方にブースに足を運んでいただき嬉しかったです。
  • @zaki___yama
    • 「新しい視点を見つけよう」というテーマにふさわしいセッションが聞けて、良い刺激になりました!
  • @toshi-toma
    • 初めての参加でしたが、福岡のエンジニアの方とたくさん交流できて楽しかったです。規模も大きく、面白いセッションが多かったです。
  • @pirosikick
    • セッションも充実していましたし、挽きたてのコーヒーやおしゃれなノベルティなど、昨年よりもさらにパワーアップしており、感動しました。

運営のみなさま、本当にお疲れさまでした。素敵なカンファレンスをありがとうございました!

スポンサーとして

最後に、今回スポンサーとして、配布物を作成し、ブースを出しましたので、そちらについても少しご紹介させてください。

配布物

チームで毎週火曜日に「フロントエンドランチ」という会を開いており、その週に各メンバーが気になった Web フロントエンドに関する記事・動画などを紹介したり、その週のjser.infoを眺めながらあーだこーだ言っています。

今回、カンファレンスで配布した印刷物には、その会で紹介した記事の中で各メンバーが印象に残ったものをピックアップして載せました。

FECF2019で配布したチラシ
FECF2019で配布したチラシ

ブース

サイボウズのブースでは、製品のパンフレットやノベルティの配布だけではなく、大きめのディスプレイを置いてモブプログラミングのデモを行いました。

サイボウズではモブプログラミングを取り入れているチームが多くあり、我々フロントエンドエキスパートチームも同様にモブプログラミングで日々のタスクを消化しています。実はこのブログもモブプログラミングで意見を出し合いながら書き上げました。

また、東京・大阪・松山・福岡と 4 拠点にメンバーが居るので、Zoom を使ってのリモートでモブプログラミングを行っています。ブースでは、我々が管理している npm パッケージの TSLint から ESLint に移行するモブを実施しました。

カンファレンス中に移行が完了し、npm publish も行えました!

今回のカンファレンスのテーマは「新しい視点を見つけよう」でしたが、まだ浸透していないモブプログラミングをデモすることでそのテーマに少しでも貢献できていたら幸いです。みなさんもぜひ、モブプログラミングやりましょう!

当日までの準備風景

弊社にはコネクト支援チームというエンジニアの対外的な活動を支えてくれるチームがあります。

カンファレンスの準備は、当然ながら業務中に行なっており、私たちフロントエンドエキスパートチームだけでは準備の時間が足りず、クオリティの担保ができませんでした。

そこでコネクト支援チームの方に協力していただき、ブース準備、配布物、当日の荷物の発送までたくさんお世話になりました。 自分たちがやりたいことができたのもコネクト支援チームのおかげです!!!!

コネクト支援チームが華麗に進めていく様子
コネクト支援チームが華麗に進めていく様子
コネクト支援チームのhokatomo作のセッション間のCM素材
セッション間のCMもhokatomo作
Twitterアイコンの缶バッチ
名刺代わりにTwitterアイコン缶バッチを作ってもらいました

We Are Hiring!!

フロントエンドエキスパートチームでは採用中です。東京・大阪・松山・福岡の 4 拠点にメンバーが居ますので、働く場所はどこでも OK です。 福岡はエンジニアが穴井だけなので、特に絶賛募集中です。

詳しい採用情報はこちら

 

Argo CDによる継続的デリバリーのベストプラクティスとその実装

こんにちは。Necoの池添(@zoetro)です。 現在San Diegoで開催されているKubeCon 2019に参加しているのですが、時差ボケで寝付けないのでこんなブログを書いています。

さて、現在我々はKubernetes上のアプリケーションの継続的デリバリーを実現するためにArgo CDというツールを利用しています。

github.com

本記事ではArgo CDについて簡単に解説した後、継続的デリバリーのベストプラクティスと具体的な実践例を紹介したいと思います。

Argo CD とは

Kubernetes向けの継続的デリバリーツールとしては、SpinnakerJenkins Xなどが有名です。 これらのツールは継続的デリバリーのパイプラインを統合的に管理・実行するためのツールになっています。

一方のArgo CDは、パイプライン全体を管理するのではなくパイプラインの中の1つの処理として動くコンポーネントになります。 このようなツールは継続的デリバリーコンポーネントと呼ばれることもあります。

今年の3月には、SpinnakerやJenkinsなどのプロジェクトを対象にContinuous Delivery Foundationが発足しました。 一方のArgo CDを開発しているIntuite社は、競合プロダクトであるFluxを開発しているWeave Works社と協力して、GitOpsの継続的デリバリーツールを開発していくようです。

www.intuit.com

これからの継続的デリバリーツール界隈は面白くなっていきそうですね。

さて、Argo CDの仕組みを簡単に解説したいと思います。

Argo CD continuous deployments
Introducing Argo CD — Declarative Continuous Delivery for Kubernetesより引用

図に示すように、開発者がアプリケーションのコードをGitリポジトリにPushすると、CIによってビルドされ、 コンテナイメージがコンテナリポジトリに登録されます。 そしてアプリケーションをデプロイするための設定(マニフェスト)をGitリポジトリにPushすると、 Argo CDはそのマニフェストをKubernetesクラスタに適用します。

このようにGitリポジトリに登録されている設定に基づいてデプロイする手法を GitOpsと呼びます。

Best Practices

先日、Argo CDのブログにて、5 GitOps Practicesという記事が公開されました。

この記事では以下の5つの項目をGitOpsのベストプラクティスとして紹介しています。詳細については上記の記事をご覧ください。

  1. Two Repos: One For App Source Code, Another For Manifests
  2. Choose The Right Number Of Deployment Config Repos
  3. Test Your Manifests Before You Commit
  4. Git Manifests Should Not Change Due To External Changes
  5. Plan How You’ll Manage Secrets

NecoプロジェクトではGitOpsによる継続的デリバリーの方法を試行錯誤していたのですが、 幸いにもこれらのプラクティスをすべて実践していました。

ここでは、我々がこれらのプラクティスをどのように実装しているのかを具体的に説明していきたいと思います。

1. Two Repos: One For App Source Code, Another For Manifests

1つ目は、アプリケーションのソースコードのリポジトリと、マニフェストを管理するリポジトリを分離せよというプラクティスです。

Necoプロジェクトでは、マニフェストをneco-appsというリポジトリで管理し、 アプリケーションのソースコードはそれぞれのアプリケーションのリポジトリで管理しています。

neco-apps

Argo CDはHelmやKustomizeなど、いくつかのマニフェストレンダリングツールに対応しています。 neco-appsでは環境ごとの差分管理や、後述するOff-the-Shelf Configurationの利用のしやすさを考慮して、 Kustomizeを採用しています。

また、Gitブランチを利用して環境ごとの適用戦略を定めています。

masterブランチに適用されたマニフェストは、毎晩テストが実行されstageブランチにマージされます。 stageブランチにマージされたマニフェストは、Argo CDによりステージング環境に自動的にデプロイされます。

ステージング環境でしばらくの間、問題なく動作することが確認できたら、手動でstageブランチをreleaseブランチにマージします。 releaseブランチにマージされたマニフェストは、Argo CDにより本番環境に自動的にデプロイされます。

このような戦略により、各環境への継続的デリバリーが実現できています。

2. Choose The Right Number Of Deployment Config Repos

2つ目はマニフェストを管理するリポジトリの数を適切に選ぶというプラクティスです。

サイボウズではKubernetesクラスタの構築と運用をおこなっているNecoプロジェクトのメンバーと、 アプリケーションを開発しKubernetesクラスタ上で運用するメンバーでチームがわかれます。

これらのチームごとにマニフェストを管理するリポジトリを用意しています。

また、Argo CDにはProjectという、 アプリケーションのデプロイ設定をグルーピングして管理する仕組みがあります。

Projectでは、利用可能なリポジトリやデプロイ先のKubernetesクラスタやnamespaceを制限することが可能です。 開発チームは特定のnamespaceにしかアプリケーションをデプロイできないように制限しています。

このようにマニフェストのリポジトリを分離しProjectの機能を活用することで、 各チームは他のチームに影響されることなく、自分たちのタイミングで自由にデプロイをおこなうことが可能になっています。

3. Test Your Manifests Before You Commit

3番目はコミット前にマニフェストをテストせよというプラクティスです。

Necoプロジェクトでは、マニフェストのテストとして三段階のテストを用意しています。

1つはマニフェストのレンダリングを実行し、そのマニフェストのバリデーションをおこなうテストです。

2番目はkindを利用した簡易テストです。 マニフェストがKubernetesクラスタにデプロイできることや、デプロイしたソフトウェアの基本的な機能が利用できることを確認しています。

最後の1つは、以前紹介した仮想データセンター上でのテストです。 データセンターを模擬した仮想環境を利用して、本番とそっくりな環境でのテストが可能になっています。

この仮想データセンター上で、kindで実施している基本的なテストに加え、マニフェストのアップグレードテストもおこなっています。 アップグレードテストでは、現在ステージング環境や本番環境に適用されているマニフェストを用いて環境を構築し、 そこに最新のマニフェストにアップグレードして、環境が壊れないかどうかを確認しています。 これまでこのテストで数多くの不具合を検出することができており、非常に有用なテストであると感じています。

ただし、この仮想データセンターでのテストは実行に時間を要するため、基本的にはstageブランチにマージする前だけに 実行するようにしています。

4. Git Manifests Should Not Change Due To External Changes

4つ目は、外部の変化の影響を受けて、マニフェストが変わってしまわないようにするというプラクティスです。

Necoプロジェクトでは、自前アプリケーションだけでなく外部のOSSのアプリケーションについても、 DockerHubなどに登録されているコンテナイメージを利用せず、neco-containersという リポジトリで管理し、自前でビルドして利用しています。

コンテナをビルドする際には、latestタグは付与せず必ず固定のタグをつけるようにしています。 そして、マニフェストではこの固定タグを指定しています。

これにより、一度リリースされたneco-appsのマニフェストは、いつ利用しても必ず 同じコンテナイメージを指すことが保証できます。

5. Plan How You’ll Manage Secrets

最後は秘密情報を適切に管理せよというプラクティスです。

GitOpsにおける秘密情報管理の決定的な方法がまだなく、様々な手法が提案されている状況です。

我々はまず秘密情報を下記の2つに分類しました。

  1. 漏洩した場合にデータセンターに侵入されたり顧客情報の流出につながる可能性のある秘密情報
  2. 1.に該当しない秘密情報

1.に関してはGitリポジトリでの管理は諦めることにしました。 手動のオペレーションが発生してしまうのですが、そもそも1.に該当する秘密情報は数が少なく 変更することもほとんどないため、運用でそれほど困ることはありません。

一方、2.に関してはGitHubのプライベートリポジトリで暗号化せずに管理し、 Argo CDで自動的にデプロイするようにしています。

Deep Dive into Argo CD

ここからは「5 Best Practices」では紹介されていない、我々がおこなっているプラクティスを紹介したいと思います。

App of Apps Pattern

Argo CDでは、Gitリポジトリから取得してKubernetesにデプロイするマニフェストの単位をアプリケーションと呼びます。 このアプリケーション設定自体もKubernetesリソースであるため、Argo CDで管理することが可能です。

また、複数のアプリケーションを管理するアプリケーションを作ることを App of Apps Pattern と呼びます。

neco-appsでは、以下のようにApp of Apps Patternのマニフェストを用意しています。

App of Apps Patternでアプリケーションリソースを管理すると、Argo CDで管理するアプリケーションが増減したとしても、 Web UIやコマンドラインでArgo CDを操作する必要はなく、マニフェストを追加してGitにPushするだけですみます。

Self Management

Argo CDもKubernetes上で動作するアプリケーションの一つであるため、Argo CDでArgo CD自身を継続的デリバリーすることが可能です。

Self Managementにより、Argo CDの更新も他のアプリケーションと同様におこなえるようになります。

マニフェストの適用状況をモニタリングする

前述したようなテストを実施していたとしても、実際にKubernetesクラスタにデプロイしてみると失敗することがあります。

Argo CDではPrometheus形式のメトリクスを公開しており、アプリケーションのヘルス情報や同期の状態を監視することができます。

Necoプロジェクトでは、Argo CD自体が一定期間ダウンしている場合や、同期の成功や失敗した情報をSlackで通知するようにしています。

Off-the-Shelf Configuration

neco-appsには、自社開発のアプリのマニフェストだけではなく、 Prometheusやcert-manager、Contourなど数多くのOSSのマニフェストが含まれています。

このようなOSSのマニフェストは、GitHub上のファイルとして配置されているもの、 Helm Chartとして配布されているもの、ドキュメントの中に埋め込まれているものなど、 配布方法は様々です。

また、配布されているマニフェストがそのまま利用できることは稀であり、マニフェストになんらかの変更を加えてから、 自分たちのリポジトリに追加することになります。 このように他から持ってきたマニフェストを取り込んで利用することをOff-the-Shelf Configurationと呼びます。

しばらく運用していると、このようなマニフェストはアップストリームのバージョンアップに 追従することが大変手間だと分かってきました。 配布方式に応じて変更点を確認し、自分たちが加えた変更を考慮しつつマニフェストのアップデートをおこなうのは骨が折れます。

Kustomizeでは、既存のマニフェストに変更点をパッチとして適用することができます。 そこでOSSで配布されているマニフェストを変更せず、そのまま自分たちのリポジトリに取り込み、 変更点のみをパッチで管理する方式を模索しています。

この方式であれば、アップストリームでマニフェストが変更されたとしても、そのマニフェストをそのまま持ってきて 上書きしてしまえばアップデートは完了します。

まとめ

本記事では、Argo CDを利用したGitOpsの実践例を紹介しました。 我々が試行錯誤してたどり着いたプラクティスなので、ぜひ参考にしてみてください。