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

Neco のネットワーク - 実装編

$
0
0

Neco プロジェクトの ymmtです。Neco は cybozu.com のインフラを刷新するプロジェクトで、先日は全面的に見直したネットワークのアーキテクチャと設計をご紹介しました。

blog.cybozu.io

簡単にまとめると、spine-leaf ネットワークを実現するにあたって、全てのサーバーで BGP + BFD + ECMPを使うことで Layer-2 拡張技術を使うことなく経路の冗長化と障害時の高速な経路収束を達成しましょうという内容でした。

今回は具体的な実装方法について解説します。ただ、かなり前提知識が必要となるため以下二つのチュートリアルを別に用意しました。BGP や BIRD に詳しくない方はまずこちらをご覧ください。

本記事で解説するのは以下の内容です。長文になりますのでお時間のあるときにどうぞ。

  1. AS 構成の選択
  2. ラック内の構成
  3. 代表 IP アドレスの実装と注意点
  4. ICMPリダイレクトを避ける工夫
  5. Kubernetes への組込み

AS 構成の選択

Neco では全てのサーバーが BGP ルータとなります。ということは、全てのサーバーに異なる AS 番号を持たせて全て eBGP でピアリングすることが可能です。これを Calicoでは AS per Compute Serverモデルと呼んでいます。

次の選択肢としては、同一ラック内のサーバーと Top-of-Rack (ToR) スイッチ 2 台は全て同じ AS 番号とし、ラック内は iBGP、ToR スイッチと Spine スイッチ間は eBGP という構成が考えられます。これを Calico では AS per Rackモデルと呼んでいます。

他のバリエーションも考えられますが基本的にはこのどちらかの選択となるでしょう。どちらを選んでも構築は可能ですが、違いを簡単に比較してみたのが以下の表となります。

項目 AS per Compute Server AS per Rack
AS 数 概ねサーバー台数と同じ 概ねラック数と同じ
BGP設定 量は多いが単純 量は少ないが iBGP に注意が必要

Neco では AS per Rack構成を採用しました。主な理由は、ToR/Spine スイッチを管理するネットワークチームからの要請です。As per Compute Server モデルではネットワークチームの管理が及ばないサーバーが個別に AS 番号を名乗ることになるので、これを避けました。

以下の図のように、ラック内の ToR スイッチ 2 台とサーバーで一つの AS を構成します。

f:id:cybozuinsideout:20181029013618p:plain

ラック内の構成

前述のようにラック内は iBGP で ToR スイッチ 2 台と全てのサーバーは同一の AS 番号を持ちます。MC-LAGのような L2 拡張技術は使わないので、ToR スイッチは一体ではなく、それぞれ別の L2 サブネットを有します。

iBGP 構成を簡略化するため、各 ToR スイッチはルートリフレクターとして動作します。そうすることでラック内のサーバーは ToR スイッチ 2 台とのみピアリングすれば良く、ラック内の他サーバーと直接ピアリングする設定を書かなくて済みます。

各サーバーは ToR スイッチ二台と接続するために NIC のポートを二つ持ちます。別々の L2 ドメインに接続するわけですから、ポート 1 とポート 2 で異なるサブネットのアドレスが割り当てられます。

この L2 サブネットの利用範囲は非常に限定的です。一つは、ネットワークブートするために DHCP でブロードキャストするために使われます。ネットワークブート後は、ToR スイッチとのピアリングにのみ使われます。その他の通信の用途には、次に説明する代表 IP アドレスを使用します。

f:id:cybozuinsideout:20181029023526p:plain

代表 IP アドレスの実装と注意点

二つの NIC ポートが持つアドレスをそのままサーバーのアドレスとするのは、いくつか問題があります。

  1. ToR スイッチが持つ L2 サブネットのアドレスなので、スイッチ故障時に到達できなくなる
  2. Kubernetes ではサーバー(Node)のアドレスは原則として一つ

2 の例としては、Pod が動作しているホストの IP アドレスの表現(status.hostIP)などがあります。

そこで、各サーバーに ToR スイッチが持つサブネットとは無関係な代表 IP アドレスを持たせます。この代表 IP アドレスは /32 のネットマスクをつけて BGP で経路広告することで、他のサーバーがアクセスできるようになります。

dummyデバイスによる実装

Linux の場合、dummyデバイスを以下のように作成すれば代表 IP アドレスを持たせられます。

$ sudo ip link add node0 type dummy
$ sudo ip link set node0 up
$ sudo ip address add 10.1.0.1/32 dev node0

node0dummyデバイスとして作られます。node0のアドレスを経路広告するには BIRD に以下の設定を入れます。

protocol direct {
    ipv4;
    interface "node0";
}

protocol kernel {
    merge paths;
    ipv4 {
        export filter {
            if source = RTS_DEVICE then reject;
            accept;
        };
    };
}

protocol directnode0のアドレスを BIRD の master4ルーティングテーブルにインポートしています。

master4で BGP ピアと経路交換して Linux カーネルに必要な経路を登録するのが protocol kernelです。この際に node0のアドレスは自サーバーのものですので、カーネルに再登録しないようフィルターしているのが if source = RTS_DEVICE then reject;です。

不定送信元アドレス問題

もう一つ、node0を代表アドレスにするためにやらなければいけないことがあります。それは node0以外のリンクが持つ IP アドレスのスコープを link localに制限することです。例えば eno1 のアドレスを systemd-networkdで設定している場合は以下のように Scope=linkを指定します。

[Match]
Name=eno1

[Address]
Address=10.1.0.65/26
Scope=link

Linux カーネルは、スコープが globalなアドレスであれば、どのアドレスであっても直接 NIC が所属していない宛先 IP アドレスのパケットの送信元アドレスに利用してしまいます。そのため node0以外に NIC のポート二つが持つアドレスのスコープを globalのままにしておくと、3 つのアドレスのどれが送信元アドレスに使われるか不定となってしまうのです。

余談ですが、この問題に気付いた当初は Linux カーネルの不具合の可能性もあるかと、再現コードを準備して LKML で質問しました。その結果上記の動作が仕様であることと、アドレスのスコープを制限する対策についてヒントをいただけて無事解決できました。メールアーカイブで一連の投稿が読めますのでご興味ある方はどうぞ。

ICMPリダイレクトを避ける工夫

AS per Rack モデルなのでラック内のサーバーは ToR スイッチ 2 台と iBGP でピアリングします。そしてフルメッシュ構成を避けようと ToR スイッチをルートリフレクターにすると、少し難しい問題が発生する可能性があります。

  1. ToR スイッチが eBGP で受け取った経路の NEXT_HOPアドレスは iBGP であるため各サーバーにそのまま届く
  2. 各サーバーにとってその NEXT_HOPは外部のネットワークアドレスであるため未知である場合がある
  3. 未知の NEXT_HOPアドレスを持つ経路を BIRD は受け取らず捨ててしまう

よくある対処その 1 は、未知にならないように当該アドレスを含む経路を同時に BGP で広報する方法です。一般的なルータはこの方法で対処できることが多いのですが、残念ながら BIRD は BGP で同時に広報されたとしても受け取りません。

よくある対処その 2 は OSPF 等の IGP で当該アドレスの経路を広報する方法です。これは BIRD でも有効ですが、せっかく BGP にまとめようとしているのに IGP を併用するのは嬉しくありません。

よくある対処その 3 は next-hop-selfを ToR スイッチで設定する方法です。これは eBGP 経路の NEXT_HOPアドレスを ToR スイッチのアドレスに書き換えて、未知アドレスではなくするものです。

next-hop-self + ルートリフレクターで発生する問題

next-hop-selfは通常パケットの送り先となるルータを NEXT_HOPにするので大きな問題になりにくい設定です。しかしながら、ここまで解説してきた:

  • 同一 L2 ドメイン内に多数 BGP ルータが存在して
  • ToR スイッチがルートリフレクターである

ケースでは、本来直接パケットを届けることができる同一ラック(L2)内のサーバーへの経路も ToR スイッチのアドレスに書き換えられてしまいます。すると下図のように、ToR スイッチはパケット送信元に ICMP リダイレクトメッセージを返して直接通信するよう伝えます。

f:id:cybozuinsideout:20181030015947p:plain

ICMP リダイレクトの発生は避けたほうが効率的というばかりでなく、一部のスイッチでは BFD との併用ができないといった問題が発生する場合があります()。他にも複数の ICMP リダイレクトを避けたほうがいい理由が存在します(参考)。

next-hop-selfに頼らず BIRD の設定で解決

未知の NEXT_HOPアドレスを持つ経路を破棄してしまう問題に対するよくある対処は 1, 2, 3 とも欠点があることが分かりました。

( ˘⊖˘) 。o(待てよ?ルートリフレクターやめてフルメッシュにすればいいのでは?)

( ◠‿◠ )☛そこに気づいたか・・・消えてもらう

仕方がないので BIRD の設定を工夫して解決しました。BIRD の使いかたに書いてあるのですが、以下のようにフィルターで NEXT_HOP (gw) を条件に応じて書き換えて受け取ることで、eBGP 経路のみ ToR を NEXT_HOPとし、同一 L2 内のルータの NEXT_HOPは書き換えないということが可能です。

# BIRD が未知の NEXT_HOP を持つ経路を破棄しないように、
# ダミーでどんな経路もとりあえず受け取るテーブルを用意
ipv4 table dummytab;
protocol static dummystatic {
    ipv4 { table dummytab; };
    route 0.0.0.0/0 via "lo";
}

protocol bgp {
    ...

    ipv4 {
        igp table dummytab;  # ダミーテーブルを参照して NEXT_HOP が未知でも受け取り
        gateway recursive;   # direct 接続時も IGP テーブルを参照させる
        import filter {
            # ラック内 AS (iBGP ピア)からの経路なら bgp_next_hop を維持
            # 以下の式は送信元ルータのアドレスを 26bit サブネットに含まれるかの判定
            if bgp_next_hop.mask(26) = from.mask(26) then {
                gw = bgp_next_hop;
                accept;
            }

            # そうでないなら from をゲートウェイに(next hop self 相当)
            gw = from;
            accept;
        };
    };
}

Kubernetes への組込み

Kubernetes のネットワークプラグインには、BGP で経路広告して Pod 間通信を実現するものが多数あります。

  • Calico: Pod に /32 な IP を割り当てて組込みの BIRD で経路広告
  • Romana: 経路数を減らせるよう Pod アドレスをブロック単位で BGP 経路広告
  • kube-router: CNI bridge プラグインと BGP の組み合わせ

Neco では Pod だけでなく、データセンターネットワーク全体を BGP で制御するため、既に全サーバー上で複雑な設定を持つ BIRD が動作しています。Calico は BIRD を組込んでしまっているため、Neco の BIRD と連携させるのが困難で選択肢から外しました。余談ですが Calico は MetalLBとの組み合わせも難しいようです(参考)。

kube-router と Romana を比較すると、Romana は BIRD を直接組込まず、BGP による経路広告はオプション機能としている点や、柔軟なアドレス割り当て方式が優れていると判断しました。

このようにネットワーク設計段階では Romana を採用するつもりだったのですが、実装する段階で様々な点を考慮して自社製のネットワークプラグイン Coilを開発する方針に変更しました。設計開始から実装終了まで 2 週間で、すでに完成しています。

Coil の紹介はまた機会を改めて。

まとめ

Neco のデータセンターネットワークの実装にあたり解決が必要だった諸問題と、その解決方法を紹介しました。基本的なところはすでに動作しており、今後は CiliumIstio, MetalLB と組み合わせて高度な機能を実装していきます。

Neco プロジェクトではネットワークやストレージ、コンテナ技術などを一緒に追求するメンバーを募集しています。Neco チーム専属の採用枠も用意していますので、エントリーをお待ちしています!

www.wantedly.com


サイボウズサマーインターン2018 報告その6 〜Webサービス開発コース(Garoon)

$
0
0

こんにちは!Garoon開発チームの洲﨑です。この夏に開催されたエンジニア向けのサマーインターンシップ、前回はUX/UIデザイナーコースの紹介でした。今回は「Webサービス開発コース(Garoon)」の紹介します!

f:id:cybozuinsideout:20181018175745j:plain

Garoon参戦!

これまでkintoneのみの開催であったWeb開発コースですが、新たにGaroonでも開催することになりました! 5日間、東京オフィスでGaroonの製品コードに触れてもらい、プロトタイプを実装してもらいました!

Webサービス開発コース(kintone)と似ているのですが、製品の特性から以下の点が異なります。

  • 使用言語。バックエンドはPHP
  • コード量と機能が多い。リリースされて今年で16年
  • 複数バリエーションを考慮。クラウド版に加えてオンプレ版など複数種類ある

やったこと

5日間で機能のプロトタイプを作成してもらいます。 基本的なタイムスケジュールは以下の通りです。 f:id:cybozuinsideout:20181015175527p:plain

実装してもらうプロトタイプはインターン生向けに選んだ要望機能リストの中から選んでもらいます。 プロトタイプ作成では具体的に以下のことを行いました。

  1. 外部仕様書/内部仕様書の執筆
    要件から仕様を固める。読み手を考えながら外部仕様と内部仕様を書く。
  2. コーディング
    Githubでプルリクを作成してメンターがレビュー。

ちょうどREST APIの開発を行っていたので、課題もREST API絡みのものが多かったですね。 既にあるGaroonの機能をAPI越しに操作できるようにするので、既存実装を読み解く時間が多かったと思います。

実際にGaroonで取り入れているスクラム開発も体験してもらうなど、できるだけ実際の開発形式に近い形で業務体験をしてもらいました。 プログラマの業務の雰囲気を感じ取ってもらえたのではないかと思います。

次にインターン生が作成してくれたプロトタイプの一部を紹介します。

インターン生が取り組んでくれた課題

メールのREST API(GET/POST)

Garoonには「メール」と呼ばれるメーラーとしての機能があります。それをREST API経由で操作できるようにしてくれました!

実装してくれたのはGETとPOSTで、API越しにメールの送受信できるようになりました。

さらにLINEと連携したAPIの使用例も作ってくれました。 LINEと連携して、メッセージを送るとGaroon越しにメールが送られます! f:id:cybozuinsideout:20181029102511p:plain

メモのREST API(GET/POST)

Garoonには「メモ」と呼ばれるテキストと画像を記録する機能があります。このメモをREST API経由で操作できるようにしてくれました!

テキストを記録するシンプルな機能なのですが、それに加えて画像も保存できるようにしてくれました。

こちらもAPIと合わせてChrome拡張を使ったAPIの使用例を作ってくれました。 Chrome拡張を使って今見ているサイトのURLとスクリーンショットをGaroonのメモに保存することができます! f:id:cybozuinsideout:20181029102456p:plain

技術以外のこと

さて、このインターンは開発だけではありません。 他にも、インターン中は勉強会や青野さんとのランチなどのイベントがあります。 この辺りは他のコースと共通なので割愛します。 f:id:cybozuinsideout:20181018180224j:plain

インターン生の声

実際に参加してくれた学生さんからの感想をいくつか紹介します。

私はcybozuのwebコースに参加させていただき、実際にGaroonのコードに触りました。古くからあるアプリケーションなので歴史を感じる部分もありつつ、メンターの方のサポートが手厚かったので問題なくインターンの課題を進めていくことができました。さらにメンターの方以外と話す時間なども設けていただくことができ、Webコースのみならず自分が気になっていたけど参加できなかったコースの話なども十分に聞くことができ、とても満足しています。


技術面で勉強になったのはもちろんですが、カンバンやスクラム開発をうまく運用している現場を見れたのも良かったです。天野さんとの面談が盛り上がり、楽しくキャリアパスやスクラム開発に関するお話を聞けたのは思わぬ収穫でした。全体を通して、社員のみなさんがとても楽しそうに思い思いに働いている姿が印象的でした。


GaroonのAPIの作成はとても楽しかったです.メンターの方が1:1対応していて,わからない部分があれば,すぐに聞ける環境が整っていました.そのため,実質実装3日ほどでしたが,GET/POSTの実装までたどり着くことができました.また,実際のプロダクト開発の流れと同じように進めていただいたので,就業体験としも非常に有意義な経験となりました.ありがとうございました.

まとめ

今回のWeb開発コース(Garoon)の概要をまとめると以下の通りです。

  • 規模大きめのコードに触れた
  • 既存コードを読み解いて新機能プロトタイプを作成した
  • 普段と同じ開発フローを体験した

Garoonチームの開発の雰囲気を掴んでもらえたのではないでしょうか。 今回の体験がインターン生にとって何らかの糧になってくれていれば幸いです!

報告は以上です!それでは!

React Conf 2018のKeynoteで発表されたHooks、Concurrent Reactのまとめ

$
0
0

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

10/25,26の2日間、ネバダで開催されたReact Conf 2018に参加して来ました。 今回は、気になっている人も多いKeynoteで発表されたHooksとConcurrent Reactについて紹介したいと思います。

今回紹介された内容は、2014年後半くらいからReactを見てきた中でも、最も大きな変更であると言えます。

React Conf 2018
React Conf 2018

React Conf 2018のストラップ
React Conf 2018のストラップ

カンファレンスのトーク自体はすでにYouTubeで公開されているので、全トーク観ることが出来ます。

https://www.youtube.com/playlist?list=PLPxbbTqCLbGE5AihOSExAa4wUM-P42EIJ

Hooks

Hooksは、GitHub上でも一切公開されておらず、React Conf 2018のタイミングで初めて公開された機能です。 これまでも、Stateful Function Componentという構想は語られていたので、それを実現するAPIと言えます。

トークの動画は下記です。 デモ主体でわかりやすいので、是非トークを観ることをオススメします。

また、こちらのトークでは実際にHooksを使ったデモが行われました。

Hooksは、Function Componentに対する機能です。 現時点(2018/10/31)ではまだ提案段階の仕様ですが、16.7.0-alphaのバージョンで試すことが可能です。 下記のRFCのIssueにはすでに500件以上のコメントがある通り、注目度と議論を招くAPIであることがわかります。

https://github.com/reactjs/rfcs/pull/68

まだRFCの段階にも関わらず、公式ドキュメントにも8つのセクションに渡ってHooksの解説が用意されています。

https://reactjs.org/docs/hooks-intro.html

したがって、今の段階でもこれを読んで16.7.0-alphaを使うことで実際に試してみることが可能です。

まだ提案の段階にも関わらずここまで丁寧にドキュメントが用意されているのは、Hooksの意図を正しく伝えたいということを示していると思います。

重要なポイント

現時点では、提案段階の仕様であり実験的に試してフィードバックをもらう段階です。そのため、今後APIが変更される可能性があるため、現時点でプロダクション環境で利用することは推奨されていません (Facebookではすでにプロダクションで使っているようですが)

Hooksを利用することで、これまでClass Componentでしか出来なかったことがFunction Componentでも可能になります。ただし、Class Componentも現時点では引き続きサポートされるため、既存のClass ComponentをHooksを使って書き直す必要は少なくとも今の時点ではありません。

将来的には、Function Componentだけになることが予想されますが、それは近い将来の話ではありません。 Facebookでも50000以上のReact Componentがあり、それら全てをHooksを使ったものに書き直す予定はないと言っています。

Class ComponentとHooksを使ったFunction Componentは一緒に使うことが可能なので、 新しいコードではHooksを使うなど、段階的な導入が可能です。

Hooksとは

HooksとはFunction Componentに対して追加された新しいAPIです。現状下記のHooksのAPIが提供されています。

  • Basic Hooks
    • useState
    • useEffect
    • useContext
  • Additional Hooks
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeMethods
    • useMutationEffect
    • useLayoutEffect

色々とありますが、まずはBasic Hooksとして定義されている3つのHooksだけでも知っておくといいと思います。

全てのHooksはuseから始まります。 これは後述するCustom Hooksを作成する際の命名規則としても適用されます。

Hooksの使い方

それでは、それぞれのHooksについて簡単に使い方を示します。

useState

useStateは、ComponentのLocal Stateを利用するためのHookです。 下記のようにStateの初期値を渡すと、現在の値Stateを更新するための関数を配列で返します。 useStateを使うことで、Class Componentを使うことなくLocal Stateを作成できます。

import React, {useState} from 'react';

const Counter = props => {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>count is {count}</p>
            <button onClick={() => setCount(count + 1)}>++</button>
            <button onClick={() => setCount(count - 1)}>--</button>
        </div>
    );
}

Stateを更新するための関数は、オブジェクトを渡した場合にもsetStateのようにマージされません。置き換えられます。

useEffect

useEffectは、副作用のある処理を定義するHookです。 API呼び出しやイベントの購読・解除など、componentDidMountcomponentDidUpdateなどのライフサイクルメソッドで行なっていたような処理を定義出来ます。 ただし、API呼び出しに対しては後述するSuspenseが適しているケースも多いです。

import React, {useEffect} from 'react';

const Header = props => {
    // textが更新されるたびにdocument.titleを更新する
    useEffect(() => {
        document.title = props.text;
    }, [props.text]);
    return <header>{props.text}</header>;
}

useEffectは第一引数に副作用のある処理を定義します。 第一引数のみ定義すると、Function Componentの関数が呼ばれる度にuseEffectに渡したコールバック関数も呼ばれます。 これは、componentDidMountcomponentDidUpdateそれぞれで呼び出す場合と同様と考えることができます。

useEffectの第二引数には、配列を指定出来ます。 配列を渡した場合、配列のいずれかの要素が変更されていた場合のみ、第一引数のコールバック関数が呼ばれます。 つまり上記の場合、props.textの値が変わった場合のみ、document.titleが更新されます。 これは、componentDidUpdateでPropsの値をチェックして変更があった場合のみ処理を行なっていたケースで利用できます。

useEffectに空の配列を渡すと、常に変化がないものとしてComponentのマウント時のみに、第一引数のコールバック関数が呼ばれます。 これは、componentDidMountを利用していたようなケースに利用できます。

useEffectは、関数を戻り値として返すことができます。 戻り値として返した関数はFunction Componentがアンマウントされる場合に呼び出されます。 これはサブスクリプションの登録、解除を行いたい場合に便利です。 下記は、イベントハンドラーの登録、解除をuseEffectを使って行なう例です。

import React, {useEffect} from 'react';

const Resize = props => {
    useEffect(() => {
        const handler = () => {
            // ...
        };
        window.addEventListener('resize', handler);
        // イベントを解除する関数を返す
        return () => window.removeEventListener(handler);
    }, []);
    return props.children;
}

useEffectはClass ComponentでのcomponentDidMountcomponentDidUpdateとは違い、DOM更新処理後に非同期で呼ばれます。 そのため、componentDidMountcomponentDidUpdateがDOM更新後に同期的に呼ばれることを保証したい場合には、後述のuseLayoutEffectを使用します。

useContext

useContextは文字通り、Contextを利用するためのHookです。 React.createContextで作成されるオブジェクトを引数として渡します。 ConsumerのComponentを渡すわけではない点に注意してください。

import React, {useContext} from 'react';

const ThemeContext = React.createContext('normal');

const Button = props => {
    const theme = useContext(ThemeContext);
    return (
        <button className={`${theme}-btn`} onClick={props.onClick}>
            {props.text}
        </button>
    );
}

useReducer

useReducerは、reducerinitialStateを渡すと、statedispatch関数を返すHookです。 Reduxをイメージするとわかりやすいと思います。 第三引数として、最初に発行するActionをオブジェクトとして渡すことも可能です。

import React, {useReducer} from 'react';

const reducer = (state, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
};

const Counter = () => {
    const [state, dispatch] = useReducer(reducer, 0);
    return (
        <div>
            <p>count is {state}</p>
            <button onClick={() => dispatch({type: 'INCREMENT'})}>++</button>
            <button onClick={() => dispatch({type: 'DECREMENT'})}>--</button>
        </div>
    );
};

useReducerで作成できるのはLocal Stateですが、Contextと組み合わせることで、下記のようにReduxのようなグローバルなStateを作ることも可能ではあります。

https://github.com/koba04/react-hooks-like-redux

余談ですが、Reduxに関してはすでにIssueで議論がある通り、react-reduxのconnectで行なっていたことをHooksのAPIとして提供することが予想されます。

https://github.com/reduxjs/react-redux/issues/1063

ちなみに、前述したuseStateuseReducerを使って実装されています。

https://github.com/facebook/react/blob/3db8b80e1501b161b213b0b5405590e4325a0414/packages/react-reconciler/src/ReactFiberHooks.js#L324-L332

useCallback

useCallbackは、少しわかりにくですが、メモ化された関数を返すHookです。 第二引数に配列として渡された値が変わった場合のみ、第一引数で渡したコールバック関数が再生成されます。 つまり、第二引数に配列として渡した値が変わらない限り、同じコールバックを取得できます。 これはPureComponentやReact.memoを使った場合など、子のComponentがPropsで渡された値を比較して最適化を行なっている場合に有効です。

import React, {useCallback} from 'react';

// React.memoを使った最適化
const Child = React.memo(props => (
    <div>
        <p>{props.name}({props.score})</p>
        <button onClick={props.onClick}>++</button>
    </div>
));

const Parent = () => {
    const [score, setScore] = useState(0);
    // scoreが変わった場合のみ再生成される
    const onClick = useCallback(() => {
        setScore(score + 1);
    }, [score]);
    // こう書くと毎回新しい関数が渡されてしまう
    // const onClick = () => setScore(score + 1);
    return <Child onClick={onClick} name="child" score={score} />;
};

useMemo

useMemoは、useCallbackと少し似ていますが、こちらはメモ化されたコールバックではなくを返すHookです。 第二引数に配列として渡された値が変わった場合のみ、第一引数で渡したコールバック関数を再評価して値を返します。 例えば、Propsとして渡された巨大なリストをフィルタリングするような、計算に時間のかかるケースで使うことが想定されます。

import React, {useMemo} from 'react';

const ItemList = props => {
    // itemsかtypeが変わった場合のみ再評価される
    const filteredItems = useMemo(() => (
        props.items.filter(item => item.type === props.type)
    ), [props.items, props.type])
    return (
        <ul>
            {filteredItems.map(item => (
                <Item key={item.id} item={item} />
            ))}
        </ul>
    );
};

useRef

useRefは、その名前の通りRefを格納するためのオブジェクトを作成することが主な用途のHookです。 ちなみにRef以外のオブジェクトも格納できます。

import React, {useRef, useEffect} from 'react';

// マウント時にだけフォーカスをあてる
const Input = props => {
    const el = useRef(null);
    useEffect(() => {
        el.current.focus();
    }, [])
    return <input type="text" ref={el} {...props} />;
}

useImperativeMethods

useImperativeMethodsは、forwardRefを使ってRef経由で親からアクセスさせる際に、RefのオブジェクトをカスタマイズするためのHookです。 ユースケースとしては多くないと思います。

import React, {useImperativeMethods, useRef} from 'react';

const MyInput = forwardRef((props, ref) => {
    const el = useRef(null);
    useImperativeMethods(ref, () => ({
        focus: () => {
            el.current.focus();
        }
    }));
    return <input type="text" ref={el} {...props} />;
});
// ここで取得できるrefはfocusメソッドのみ持ったオブジェクト
// <MyInput ref={ref} />

useMutationEffect

useMutationEffectは、利用方法はuseEffectと同じですが、実行されるタイミングが異なるHookです。 useMutationEffectは、ReactがDOMを更新するのと同じタイミングで同期的に呼び出されます。 DOMが更新されるタイミングで処理をしたい場合に利用します。

DOM更新中に同期的に実行されるため、DOMのプロパティにアクセスすることで強制的なレイアウトの再計算が行われ、パフォーマンスに悪い影響を与える可能性があります。 基本的には利用することを避けるべきHookだと思います。

useLayoutEffect

useLayoutEffectは、useMutationEffectと似ていますが、こちらは全てのDOMの更新処理が終わったタイミングで同期的に呼ばれるHookです。 更新後のレイアウト情報をDOMから同期的に取得したい場合に利用します。 これは、Class ComponentのcomponentDidMountcomponentDidUpdateが呼ばれるタイミングと同じタイミングで呼ばれます。 DOMの更新処理の後に同期的に呼ばれるため、可能であればuseEffectを使う方が望ましいです。

useEffectuseMutationEffectuseLayoutEffectのタイミングをUser Timingで示すと下記の通りです。 useMutationEffectがHost Effects(DOMの更新処理のタイミング)に、useLayoutEffectがライフサイクルメソッド呼び出しのタイミングに、useEffectがそのあと非同期に呼ばれていることがわかります。

各Effectのタイミングの違い
各Effectのタイミングの違い

Hooksの制限

HooksはFunction Componentの関数内から呼び出す必要があります。 また、HooksはFunction Component内において、毎回同じ順番で呼び出す必要があります。 そのため、関数内のトップレベルで呼び出すことが推奨されています。条件分岐などの中で呼び出すことは避ける必要があります。

const Foo = props => {
    // OK
    const [foo, setFoo] = useState('foo');
    if (props.bar) {
        // NG
        const [bar, setBare] = useState('bar');
    }
    return null;
}

これをチェックするためのeslint-plugin-react-hooksというESLintのプラグインも同時に公開されています。

https://reactjs.org/docs/hooks-rules.html#eslint-plugin

この制限は、少しわかりにくく感じますが、HooksのAPIをシンプルにするためのトレードオフとして選択したとのことです。

Custom Hooksの作り方

Hooksは前述した通りFunction Componentから呼び出す必要がありますが、独自に定義したHooksの中で呼ぶことも可能です。 そのため、特定の処理をHooksとして共通化することが可能です。

Custom Hooksは前述したeslint-plugin-react-hooksでのチェックを有効にするためにも、useから始まる名前で作成することが推奨されています。 例えば、windowのサイズを返すHookは、下記のように作成して使用できます。 作成したHookは、Componentの描画には一切関与していないため、どこでも再利用できます。

  • useWindowSize.js
import {useState, suseEffect} from 'react';

// windowサイズを返すHook
export const useWindowSize = () => {
    const [width, setWidth] = useState(window.innerWidth);
    const [height, setHeight] = useState(window.innerHeight);
    useEffect(() => {
        const handler = () => {
            setWidth(window.innerWidth);
            setHeight(window.innerHeight);
        }
        window.addEventListener('resize', handler);
        return () => window.addEventListener(handler);
    }, []);
    return [width, height];
}
  • 作成したHookを使う
import React from 'react';
import {useWindowSize} from './useWindowSize';

const WindowSize = () => {
    const [width, height] = useWindowSize();
    return <p>width:{width}, height:{height}</p>
};

Hooksの目的

Componentのロジックを再利用するための手段としては、Higher Order Component(以下HOC)やRender Propsのパターンがありますが、 これらはコードを理解するのが難しかったり、"wrapper hell"と呼ばれる大量のComponentのネストが作成されるという問題があります。 recomposeのようなHOCのユーティリティライブラリを使っていて、気づかないうちに"wrapper hell"を作ってしまっているケースも多いと思います。 "wrapper hell"はデバッグの難しさや、見た目のViewの構造以上にComponentが大量にネストすることで、パフォーマンスに対しても影響があります。

また、サーバーからのデータ取得やイベントの購読・解除の処理をClass Componentが持つライフサイクルメソッドを使って記述しようとすると、コードが冗長になったりライフサイクル毎に処理が分断してしまう問題があります。 加えて、全てのライフサイクルメソッドの挙動を理解して適切にロジックを書くのは難しいという意見もあります。

Hooksは、Componentのロジックの再利用をするためのPrimitiveとして新しく提案されました。

例えばHooksでは、Componentのライフサイクルについて考える必要はなく、useEffectなどを使うことで「この値が変わったらこの処理をする」といったように、行いたい処理だけに注目してロジックを書くことができます。

Reactでは、Function Componentを使ったとしても内部ではFiberと呼ばれているデータ構造で状態を保持しています。 ライフサイクルメソッドがComponentに対するハイレベルなAPIであったのに比べて、HooksはFiberの内部構造に対するPrimitiveなAPIとして考えることもできます。 それは、@dan_abramovがKeynoteの中で、Atom(原子)に対するElectron(電子)のように、HooksはReactを構成する各機能の要素であると言っていることからもわかります。

React内部の仕組みについては、builderscon 2018で話したので、興味あれば見てみてください。

https://speakerdeck.com/koba04/algorithms-in-react

余談ですが、Hooksのソースを見てみると、HooksはUpdateと同様に単方向のLinked Listとして実装されているのがわかります。

https://github.com/facebook/react/blob/3db8b80e1501b161b213b0b5405590e4325a0414/packages/react-reconciler/src/ReactFiberHooks.js#L52-L60

これを、Function Componentが処理される度に初期化して順番にアクセスしていくため、必ず同じ順番に呼び出す必要があります。 他にも、useReducerの実装を見てみると、更新処理は既存のsetStateの仕組みとは違い、別の更新キューを作って処理していることがわかったりと興味深い部分も多いです。

Keynoteでは、Hooksの導入の理由としてJavaScriptにおけるClassが、機械にとっても人間にとっても扱いが難しいものである点をあげています。 例えば、イベントハンドラー登録時のthisの扱いにハマる人が多かったり、constructorなどの定型コードによりコード量が多くなったり。 また、Function Componentで書いた後に状態やライフサイクルメソッドが必要になった際に大きくコードを書き換える必要があるなど、ClassがDeveloper Experienceに与える影響を指摘しています。

また、機械にとってもClassは最適化(プロパティ名やメソッド名のminifyや処理のインライン化など)が難しいという問題点を指摘しています。 この辺りは、Prepackで取り組んでいる結果としての結論なのかなと思います。 他にもHot Reloadingも難しいという問題もあるようです。

現状、getSnapshotBeforeUpdatecomponentDidCatchgetDerivedStateFromErrorなど、一部のライフサイクルメソッドに対応するHooksが提供されていませんが、これらもいずれ提供される予定とのことです。

将来的にはHooksを使うことで、Class Componentを廃止する流れになるかと思います。 ですが、それは近い将来の話ではないので、慌てず少しずつ試していくのがいいかなと思います。

Concurrent React

Hooksが話題になる中、次の日のKeynoteではConcurrent Reactについて発表されました。

トークの動画は下記です。 デモ主体でわかりやすいので、是非トークを観ることをオススメします。

また、こちらのトークでは実際にSuspenseを使ったデモが行われました。

Concurrent Reactは、これまでAsync Renderingと呼ばれていたもので、SuspenseとTime-slicingの2つの機能を指します。 今回は新しい何かが発表されたというより、現在の状況を改めて説明して、デモでどういったことが可能になるかを示すものでした。

Suspense

Suspenseはレンダリングを中断(Suspend)できる機能です。 Suspenseは、Promise(正確にはthenableなオブジェクト)をPrimitiveとして扱うため、APIからのデータ取得やComponentの動的な読み込みだけでなく、Promiseでラップすることで様々な非同期処理に対して適用できます。

Suspenseの仕組みについては、過去に何度か紹介しているので興味あれば参照してください。 スライドで紹介しているバージョンからAPIの名前は変わっていますが、基本的な仕組みやコンセプトは同じです。

本トークでは、React.lazyと組み合わせた動的なComponent読み込みが紹介されていました。

import React from 'react';

const LazyContent = React.lazy(() => import('./LazyContent'));

const App = () => (
    <main>
        <section>
            <p>Main</p>
            <React.Suspense fallback={"loading..."}>
                <LazyContent />
            </React.Suspense>
        </section>
    </main>

);

上記では、LazyContentのComponentを動的に読み込んで、読み込まれるまではloading...のメッセージを表示しています。 上記の場合、loading...のメッセージが即座に表示されますが、後述するReact.ConcurrentModeを使うことで指定秒経過後にローディングをメッセージを出すといったことも可能になります。

上記で使用している動的なimportはまだ提案段階の仕様ですが、webpackなどを使っている場合にはすでに利用可能です。 このようにReact.lazyを使うことで、webpackなどのツールのサポートは必要ですが、importの仕方を変えるだけで簡単に動的な読み込みが可能となります。

React.SuspenseのComponentは親の位置であればどこでも配置可能なため、ローディングで隠す範囲も簡単に指定できます。 また、例えば複数の非同期な依存関係がある場合に、それらの親にReact.Suspenseを定義することで、全ての非同期の依存が解決するまで、単一のローディングメッセージを出すといったことも可能です。 このようにSuspenseを使うことで、柔軟な非同期読み込みの制御が可能です。

APIリクエストについては、react-cacheというパッケージのunstable_createResourceを利用することが下記の通り記述できます。 ただし、react-cacheについてはまだStableではなく、キャッシュのInvalidationなど欠けている機能もあるのでまだ実際に利用できるレベルではないとしています。

import React from 'react';
import {unstable_createResource as createResource } from 'react-cache';

// Promiseを返す処理からリソースを作成する
const userResource = createResource(id => {
    return fetch(`/api/user/${id}`).then(res => res.json())
});

const User = props => {
    // リソースを読み込む
    // リソースがキャッシュされていない場合は、Promiseがthrowされるのでレンダリングが止まる
    const user = userResource.read(props.id);
    return <div>{user.name}</div>;
}

const App = () => (
    <React.Suspense fallback="loading...">
        <User id={1} />
    </React.Suspense>
);

上記では、指定したユーザのデータがキャッシュになければfetchを使ってAPIから取得してキャッシュに格納します。 その際、React.Suspenseに指定しているloading...のメッセージが表示されます。 その後、APIレスポンスを受け取るとレンダリングが再開されます。 再開時には、APIレスポンスのデータがキャッシュに格納されていてデータを同期的に取得できるので、Userが表示されます。

Suspenseの応用例として、"Moving To Suspense"のトークでは、React.Suspensefallbackに低解像度の画像を指定して、高解像度の画像を非同期にロードすることで、最初は低解像度の画像を表示してそのあと高解像度の画像に差し替えるといったデモも行われてました。

また、unstable_createResourceで作成したリソースはreadだけでなくpreloadというメソッドも持っていて、これを使うことで、事前にデータをキャッシュしておくことも可能です。

Suspenseについては、上記のような同期モードでの基本的な挙動については、すでにStableだとしていますが、react-cacheを使ったAPIデータの取得やReact.ConcurrentModeを使ったConcurrentModeについては、まだまだStableでないとしています。

ConcurrentModeはレンダリングを非同期にするためのモードです。 React.ConcurrentModeのComponentで囲むことで、その中はConcurrentModeになります。 また、ReactDOM.createRoot(domElement).render(ReactElement)という新しい方法でDOMをマウントすることで、全体をConcurrentModeにすることも可能です。

部分的に非同期レンダリングを導入したい場合にはReact.ConcurrentMode、アプリケーション全体を非同期レンダリングしたい場合にはReactDOM.createRootを使います。

ConcurrentModeでは、React.SuspensemaxDurationを指定できます。 maxDurationを指定することで、fallbackを表示するまでの時間を制御できます。 例えば、ネットワーク環境がよくてAPIリクエストが1秒以内に返ってくるような状況では、ローディングを表示せずにデータのロードを待って表示した方がスムーズです。 そういった場合に、maxDuration={1000}のように指定することで、1秒経過してからローディングを表示するといった制御が可能です。

Suspenseを利用することで、非同期な依存関係の制御が簡単に柔軟にできるようになります。

また、サーバーサイドレンダリング対応についても取り組んでいるようです。

Time-slicing

Time-slicingは、更新処理を優先度付け出来る機能です。 プライオリティをベースとした協調的マルチタスクにより、メインスレッドをブロックしない更新処理を可能にします。 これにはschedulerというパッケージを利用します。

Reactでは、clickinputtouchmoveなど、ユーザーがすぐにリアクションを期待するようなイベントに対しては、InteractiveUpdateとして高い優先度が割り当てられます。 ただし、巨大なスプレッドシートに対するフィルタリングなど、一度の更新処理が重い場合にはユーザー入力や他の更新をブロックしてしまいます。 この場合、ユーザーにとってはフィルタリングするためのテキストボックスはすぐに反映されて欲しくて入力もブロックして欲しくないですが、フィルタリングした結果の表示については、少しくらい遅れても問題ないことが多いです。

このような場合に、Time-slicingを使うことで、フィルタリングするためのテキストボックスへの更新処理は優先度高く反映して、フィルタリングした結果については優先度を下げて反映を遅らせることが可能となります。

import React, {useState, useMemo} from 'react';
// このバージョンはまだnpmにpublishされていない
import {scheduleCallback} from 'scheduler';

const App = props => {
    const [text, setText] = useState('');
    const [filterText, setFilterText] = useState('');

    const filteredItems = useMemo(() => (
        props.items.filter(item => item.indexOf(filterText) !== -1)
    ), [filterText, props.items]);

    return (
        <main>
            <Input
                value={text}
                onChange={value => {
                    setText(value);
                    // Filterする方の優先度は下げる
                    scheduleCallback(() => {
                        setFilterText(value);
                    })
                }}
            />
            <List items={filteredItems}  />
        </main>

    );
};

上記のサンプルは、使用しているschedulerscheduleCallbackがまだ公開されていないため動作しませんが、以前作成したデモがあるので、そちらを試してもらうと雰囲気が掴めるかなと思います。

また、ConcurrentModeでは、hiddenのPropsによるメインスレッドを邪魔しないプリレンダリングが可能です。 hiddenのPropsをDOM Componentに指定することで、その子要素はOffScreen Priorityという特殊な優先度で処理されます。 OffScreen Priorityはとても低い優先度として定義されているため、他の更新処理をブロックしません。

トークではタブUIの例が示されていましたが、ユーザーが表示するページを先に読み込んでおくことで高速なページ遷移を実現できます。 Suspenseと組み合わせることで、事前に非同期の依存関係をロードすることが可能になるため、さらに強力な仕組みとなります。

const Home = React.lazy(() => import('./Home'));
const User = React.lazy(() => import('./User'));
const Settings = React.lazy(() => import('./Settings'));

const App = props => (
    <main>
        <div hidden={props.page === "user"}>
            <User />
        </div>
        <div hidden={props.page === "settings"}>
            <Settings />
        </div>
        <div hidden={props.page !== "user" && props.page !== "settings"}>
            <Home />
        </div>
    </main>
);

上記では、最初に表示したページ以外も動的に読み込んでプリレンダリングしています。

まとめ

このように今回発表された内容は、Reactを使ったアプリケーションの作り方を変える大きなものでした。 ここからReduxなどの周辺ライブラリがこれらのAPIをどう使うかにもよりますが、時間をかけて今回紹介された内容を使った書き方に変わっていくと思います。 まだ安定版としてリリースされたという状況ではないので、盛り上がりに踊らされず少しずつ試していくのがよさそうです。

また、Keynoteでは、この他にも新しいProfilerについての紹介もあったのでそちらも注目です。

サイボウズのフロントエンドエキスパートチームでは、一緒にフロントエンド分野の問題解決に取り組んでくれる仲間を募集しています。

インターン生 Meetupを開催しました

$
0
0

こんにちは、インターンシップ運営係の中園です。

2018月10月31日(水)に、サマーインターンシップ2018に参加した学生さんたちを招待し、「インターン生 Meetup」を開催しました!

f:id:cybozuinsideout:20181102124859j:plain

インターン期間中になかなか関われなかった入社1~2年目のエンジニアや、別の日程に参加したインターン生同士の交流を目的としたイベントです。

本記事では、インターン生 Meetupの内容をご紹介します!

パネルディスカッション

事前にインターン生のみなさんから募集しておいた質問をもとに、若手エンジニア社員を交え、東京オフィスと大阪オフィスをつないでパネルディスカッションを行いました。

f:id:cybozuinsideout:20181102182410j:plainf:id:cybozuinsideout:20181102182445j:plainf:id:cybozuinsideout:20181102181647j:plain

質問と回答をいくつかご紹介します。

Q. 普段、どのように最新技術の情報をキャッチアップしていますか?

・Twitterでエンジニア界隈の著名人をフォローしている
・社内で使っているkintoneで、技術情報がどんどん流れてくる
・週1回お昼にフロントエンドランチを開催し、技術を学んでいる
・海外のカンファレンスに業務として行っている

Q. お休みの日や仕事のすきま時間に行っていること(勉強など)はありますか?

・休日は趣味を満喫している
・業務時間中に技術の勉強はできるので、休日は技術の勉強以外に時間を使う
・仕事に関わらないけど興味のある技術を休日に学ぶことはある
・みんながみんな、休日もガリガリ勉強しているわけでもない

関連して、「業務利用しているOSSの休日個人開発は業務か?」のブログ記事を紹介しました。 blog.cybozu.io

インターン生の方からは、「業務に含めるが、業務にしたくないならしなくてもよい、みたいなのがサイボウズさんらしいなと思いました」といった感想もいただきました!

Q. どういった勉強会がどのくらいの頻度で開催されていますか?

・社内ではエンジニアに限らず1日3~4件の勉強会がどこかしらで開催されている
・実際に参加するのは週1くらい
・エンジニアが開催する勉強会は技術的な内容だけでなく、英語の勉強会も開催される
・国内の他拠点や海外拠点と繋いでリモートで開催することもある
・社内だけでなく、社外の方も参加した勉強会も開催されている

インターン生のみなさんから、「勉強をする風土があるの良いですね」「インターンシップの時、勉強会が多くてびっくりしたことを覚えています」といったコメントをいただきました!

Q. 将来どんなキャリアを歩んでいきたいですか?

・ずっと手を動かしていたい、できることを増やしていきたい
・エンジニア新人研修の初日に先輩社員から「キャリアは後からついてくる」という話を聞いて、そういう考え方もあるというのを知った
・やりたいことは入社1年くらいで変わってくるので、いろいろできる会社を選ぶのもいいかも

Q. 社内でのキャリアアップはどんな感じですか?

・週1である1on1面談で興味のあることや、やりたいことを上司と話すことができる。熱く語ってもいいし、雑談ベースで話すのもOK!
・1on1面談で、スクラムマスターへの道を考えるきっかけになった
・希望すれば、兼務や異動も可能
・「育自分休暇」という制度があるので、一度社外に出てスキルアップして、サイボウズに戻ってくることも可能
・「大人の体験入部」という制度で他部署の仕事を経験することもできる

「育自分休暇」や「大人の体験入部」についての関連記事: cybozushiki.cybozu.co.jp

www.wantedly.com

インターン生のみなさんから感想やより深い質問をいただくことができ、パネルディスカッションはあっという間に盛況のうちに終わりました!

交流会

f:id:cybozuinsideout:20181102183302j:plain

当日はハロウィンということで、料理やお菓子をハロウィン仕様にしてみました!

交流会では、インターンシップのメンター社員も合流しました。

インターン生と近況を報告し合ったり、最近の技術について語り合うなどアツい会となりました!!

f:id:cybozuinsideout:20181102184751j:plain

おわりに

インターンシップの各日程に参加した学生さんたちの交流会は今年初めての試みでしたが、みなさん楽しんでいただけていたようでした。 また来年も、インターンシップと、インターンシップ後のMeetupを開催できるといいなと思います!

各コースのインターンシップの実施報告記事がありますので、気になる方は是非ご覧ください。

blog.cybozu.io

blog.cybozu.io

blog.cybozu.io

blog.cybozu.io

blog.cybozu.io

blog.cybozu.io

イベントレポート: Cybozu UXCafe「チームで取り組む!サイボウズのアクセシビリティ」

$
0
0

こんにちは、デザイングループ の小林(@sukoyakarizumu)です。

2018年11月28日に開催したイベント「UXCafe チームで取り組む!サイボウズのアクセシビリティ」についてレポートします。

発表中の登壇者と参加者の様子

イベントの趣旨

今回のイベントは、サイボウズのさまざまな開発チームがアクセシビリティに取り組んでいることを伝えるために開催しました。

サイボウズは、複数の開発チームがアクセシビリティの活動を進めています。また、特定の職能だけではなく、デザイナー、プログラマー、QA、TC(Technical Communication)など、さまざまな職能のメンバーが一緒にアクセシビリティに取り組んでいます。

今回は、「Garoonチーム」「TCチーム」「kintoneチーム」の3つの開発チームについて、アクセシビリティに取り組むノウハウや課題を共有しました。

発表内容

トーク1: Garoonチームのアクセシビリティ

Garoonの開発チームからは、職能の違う3人が、現在Garoonチームで進めているアクセシビリティギルド活動について紹介しました。

デザイナーの河内山(@kochitaku)は、ギルド活動の歴史とデザイン業務でのチェックや所感の紹介。 Garoonはアクセシビリティがデザインリニューアルのコンセプトのひとつになる実績もあります。(参考: Garoon 新デザインの紹介ページ)

河内山の資料はこちら

プログラマーの杉山(@oogFranz)は、アクセシビリティに配慮したHTMLやCSSを書いたときに、大きな工数が必要になってしまった課題について紹介しました。 特に、複雑なUIパーツを実現する際に、キーボード操作によって工数が膨れてしまったり、CSSやWAI-ARIAの指定が煩雑になってしまう問題を共有しました。

杉山の資料はこちら

QA(品質保証)の小関は、アクセシビリティに関する効果的な不具合の検出について発表しました。QAは、機能テストの合間にアドホックにアクセシビリティの不具合を見つける活動から始めましたが、検出されても優先度が低く設定されることが多かったようです。闇雲に不具合を検出するのではなく、実装した要件ごとに、アクセシビリティ対応した部分の試験をチェックリストに沿って実施するようにしていきたいと説明していました。

小関の資料はこちら

トーク2: TCチームのアクセシビリティ

TCチームは、サイボウズ製品のヘルプサイトの制作や、製品の文言の決定・翻訳を行っています。 今回は、TCチームの山本と近藤が、サイボウズ Officeのヘルプサイトをリニューアルする際に行ったアクセシビリティ対応について紹介しました。

TCチームは、もともと社内のアクセシビリティ勉強会を通じて、アクセシビリティに取り組むと心に決めていました。 ただ、アクセスログやユーザアンケートを分析した結果から、 リニューアル前のヘルプサイトには、検索性やモバイル対応に課題があることがわかり、自然な結果として、課題を解決するためにはアクセシビリティに対応することが最適解という結論を導くことができたという話でした。

またデザインやマークアップについても共有しました。 ノウハウがあまりない中で、キーボード操作やコントラストを考慮した色の調整をするのは非常に大変で、アクセシビリティ対応を行った他チームの知見が役に立ったと、当時ことを、明るく振り返っていました。

TCチームの資料はこちら

kintoneチームのアクセシビリティ

kintoneの開発チームからは、小林(@sukoyakarizumu)が、アクセシビリティ対応を普段の開発プロセスに組み込むにあたって、うまくいったこと・いかなかったことを共有しました。

現在自分は、できるだけ開発プロセスの中にアクセシビリティを組み込むようにしています。 開発チームが普段から行っているプランニングやモブプログラミングに一緒に参加して、アクセシビリティ対応を行ったりしています。 よりアクセシビリティを浸透させていくためには、アクセシビリティを特別視せず、できるだけ開発プロセスになじませ「日常化」することが重要だと考えています。

kintoneチームの資料はこちら

総括

今回のイベントでは、サイボウズのさまざまなチームのアクセシビリティ活動について紹介しました。 複数のチームや職能のメンバーが取り組んでいることに感銘を受けたという意見もいただけました。

それぞれの創意工夫で様々な活動を行われていますが、すべてのチームに共通して言えることは「チームワークあふれる社会をつくる」というサイボウズの理想に根差してアクセシビリティ対応を進めていることだと思います。 これからも、多くのユーザがチームにアクセスできる製品づくりを進めていきます。

KubeCon + CloudNativeCon North America 2018 現地レポート 0日目

$
0
0

こんにちは、Necoプロジェクトの @ueokandeです。 現在、Necoのプロジェクトメンバーは全員で、KubeCon + CloudNativeCon North America 2018に参加しています。 今日から4日間、KubeCon + CloudNativeCon North America 2018の現地レポートをお送りしたいと思います!

KubeCon + CloudNativeConは CNCF (Cloud Native Computing Foundation) が主催するカンファレンスで、コンテナ関連のカンファレンスでは世界最大です。 これまでに世界各地で開催されており、2018年は以下の場所で行われました。

  • 2018年05月: KubeCon + CloudNativeCon Europe 2018
  • 2018年11月: KubeCon + CloudNativeCon China 2018

今回のKubeCon + CloudNativeCon North America 2018はシアトルで開催され、ヨーロッパ、中国開催よりも大規模です。 参加者も年々増えており、今回は7000人を超える人たちが参加しています。

KubeCon + CloudNativeCon自体は12月11日から13日の3日間開催されます。 前日の今日は、Co-Located Eventという、クラウドベンダーやコミュニティによるキーノートやワークショップが開催されてます。 自分は数あるCo-Located Eventの中の一つ「DigitalOcean TIDE: Kubernetes Unconference」に参加したので、簡単にレポートしたいと思います。

DigitalOcean TIDE: Kubernetes Unconference

DigitalOcean TIDEはDigitalOceanが開催してる技術カンファレンスの名前です。 タイトルにもある通りUnconferenceという形式で開催されました。

Unconferenceとは参加者が主体となって、話したいことや意見交換をします。 つまりこの会議ではアジェンダもなければ、だれが何を話すかも決まってません。 ディスカッションの内容は、参加者からいくつか募って、他の参加者は興味のあるディスカッションに参加するという形です。

f:id:cybozuinsideout:20181211154637j:plain
Uncounferenceで出された話題
f:id:cybozuinsideout:20181211154716j:plain
Unconferenceのディスカッションの様子

8つのディスカッションが並行開催して、それが4回繰り返されました。 自分は以下のディスカッションに参加しました。

  • モニタリングとメトリクス
  • Kubernetes名前空間とセキュリティ
  • KubernetesとOSの更新
  • ベアメタル上のKubernetes運用

司会者の英語がうまく聞き取れないと感じたとき、正直最初は無理しすぎたと感じました。 実際ディスカッションに参加するどころか話を聞き取るだけで精いっぱいでした。 しかし普通のセッションと違って、いろんな環境や要件があって面白かったです。 単なる成功談だけではなく、幅広いKubernetes運用の環境方法について知れたのが興味深かったです。

今回は聞くだけともったいないことをしたので、英語力を鍛えてまたいつか再チャレンジしたいと思います。

Lightning Talk

全てのCo-Located Eventが終了すると、Lightning Talkが開催されました。 Lightning Talkは一番大きな会場で行われ、おそらくほとんどの参加者が参加してたようでした。

f:id:cybozuinsideout:20181211154742j:plain
Lightning Talkの様子

タイトルも18タイトルと盛りだくさんでした。 その中で印象に残ったタイトルをいくつか挙げてみます。

  • Behind The Scenes: Kubernetes Release Notes Tips & Tricks - Mike Arpaia, Kolide: Kubernetesの機能追加とドキュメントリリースまでの流れの紹介でした。 ドキュメントの書き方や書くタイミングを紹介したり、BOTによるPull Requestの自動処理の方法について紹介されてました。
  • Spawning Kubernetes In CI For Integration Tests - Marko Mudrinić, Loodse: Kubernetes上の試験自動化のアプローチの紹介で、kind (Kubernetes in Docker) と呼ばれるツールの紹介です。 kindはテストなどCI環境で使うためのKubernetesクラスタで、実際Travis CIで利用できるそうです。 Necoでもテスト自動化にも力を入れてるので、参考にしていきたいです。
  • Optimizing Kubernetes Networking at Datadog - Laurent Bernaille, Datadog: Kubernetesを構築するとき、どういうネットワーク構成が良いのかというお話です。 本番環境で利用できるようなネットワークをKubernetesに構築するには「IPVS (ネイティブなロードバランシング)」「No bridging (ホスト上でルーティング)」「Native Pod routing (Podは通常のIPを利用する)」の3つが重要になるそうです。 3つ目のNative Pod routingは、オンプレ運用ではBGPを利用したネットワークを構築します。 NecoでもBGPネットワークでPodにIPアドレスを割り当てており、自分たちのプロジェクトに重なる部分がありました。

まとめ

以上で、KubeCon + CloudNativeCon North America 2018のCo-Located Eventの紹介でした。 まだKubeConが始まってないのに、すでに緊張と楽しみでわくわくです。 明日はKubeConの1日目のレポートをお送りしたいと思います。 お楽しみください!

KubeCon + CloudNativeCon North America 2018 現地レポート 1日目

$
0
0

こんにちは、Necoプロジェクトの mitz です。 続いて1日目のレポートになります。Co-Located Eventが終了し、いよいよイベント本番を迎えました。前日のレポートはこちら

blog.cybozu.io

私も昨日は Kubesec 2018 Enterprise SummitというKubernetesにおけるセキュリティに絞ったイベントに参加していました。 KubeCon + CloudNativeConはどこの開催でもCo-Located Eventがありますので、準備運動という意味でも0日目から参加する ことをおすすめします!

最終的には CNCF channel にほぼ全てのセッションが公開されるので乞うご期待です。

www.youtube.com

イベント全体の雰囲気や、数多くのセッションの中で印象に残った発表などを簡単に紹介していきます。

Keynote

Keynote: Opening Remarks + CNCF Community Updates – Liz Rice, Technology Evangelist, Aqua Security

まずは参加者への挨拶とCNCF Communityのアップデートを共有しました。

CNCFはコンテナ化を中心にクラウド環境に特化した(Cloud Native)OSSを推進するLinux Foundation傘下の非営利団体です。 今年最後のイベントであり多くの国から参加しています。参加者はついに8000人を超えました。私が今まで参加したOSSのイベントでは過去最大です。

CNCFは Landscape というWebページを公開しており、Cloud Nativeなソフトウェアを整理してまとめています。カテゴリ分けもされており、これからクラウド基盤上にソフトウェアを 動かしたり開発したりする人たちにとても有益です。

l.cncf.io

CNCFが管理するOSS Projectはキャズム理論に基づいて3つの段階で成熟度を評価します。Graduated(Kubernetes, Prometheus)、Incubation(CoreDNS, Harbor etc)、Sandbox(OpenMetrics, TiKV etc)といった感じです。 これによってどれほど世界中に浸透した製品なのか、盛り上がりのあるプロジェクトなのかが分かるようになります。Kubernetesはすでに世界中で本番環境で使われているContainer Orchestrationであり、K8sを使い方や将来の機能を見届けるために参加してきた人たちも多いと思います。

最近のIncubator projectになったのが HarborというContainer Registryサービスを構築するソフトウェアです。Cloud NativeなContainer Registryであり、Kubernetes、Apache Mesosなどの上でデプロイできるようになっています。ちなみにCloud Nativeの定義はこちらにありますが、つまりコンテナ駆動、コンポーネント間の疎結合、宣言型API、Hybrid Cloudなどに最適化されているものを指します。

Keynote: 5 Years of etcd: Past, Present, and Future - Brandon Philips, CTO CoreOS, Red Hat & Xiang Li, Senior Staff Software Engineer, Alibaba

etcdの5年間の歴史を語っています。etcdは代表的なKey-Value分散データベースでKubernetesだけでなく多くのソフトウェアのバックエンドで採用されています。もちろんNecoのソフトウェアでも使用しています。etcd v3になってから信頼性、利便性、効率性などが上がりました。

f:id:cybozuinsideout:20181212161547j:plain

Keynote: Tales from the Kube: Contributions by the Community - Brian Grant, Principal Engineer & Tim Hockin, Principal Software Engineer, Google & Clayton Coleman, Architect, Kubernetes and OpenShift, Red Hat

CNCF、K8sの立ち上げメンバーが語る歴史です。Kubernetesは少数メンバーの開発だけでは成り立たず、個人や企業による貢献によって現在まで浸透したソフトウェアです。クラウドにおける重要機能としてMultitenancyがありますが、namespaceの実装がそれを実現しました。CNI(Container Netowrk Interface)によるコンテナ向けネットワーキングもCoreOSのrktから始まり、DockerのようなAll-in-Oneの難点を克服した解決方法でした。現在はIoT分野にもKubernetesの関心が出てきており、Raspberry PiでKubernetes Clusterを構築した学生もいます。改めて全てのContributorに感謝の意を込めていました。

f:id:cybozuinsideout:20181212161711j:plainf:id:cybozuinsideout:20181212172809j:plain

ちなみに感謝のスライドは文字が大きいほどその国のContributorが多いです。

Keynote: High Reliability Infrastructure Migrations - Julia Evans, Software Engineer, Stripe

最も感銘を受けたKeynoteでした。高いSLAを求められる企業と新しいインフラに入れ替えることにおけるいろいろな教訓を語りました。全て手書きのスライドでとても特徴的です。新しいソフトウェアを導入するにはまず自分がそれのExpertになること、障害は共有することがインフラの信頼性を上げることなどインフラエンジニアたちが感じるポイントを的確に抑えています。

f:id:cybozuinsideout:20181212163116j:plainf:id:cybozuinsideout:20181212163117j:plainf:id:cybozuinsideout:20181212163126j:plainf:id:cybozuinsideout:20181212163301j:plain

CFP Tracks

Behind Your PR: How Kubernetes Uses Kubernetes to Run Kubernetes CI - Sen Lu & Benjamin Elder, Google

Kuberentes開発のPull Requestの中で行われる仕組みをテストを中心に紹介しています。当初はGCPでテスト用のインスタンスを立ち上げてテストを流すというものでした。しかし現在は10000 job/日のテストが実行されており、スケーラビリティの向上が課題になっていました。現在は Prow(test-infra/prow at master · kubernetes/test-infra · GitHub )という CI/CD system で可動しており、専用のControllerとCRD(Custom Resource Definition)、Kubernetes cluster上にKubernetesを構築する形で実現しています。

f:id:cybozuinsideout:20181212161932j:plainf:id:cybozuinsideout:20181212161942j:plain

What's Next for etcd Cluster Management? - Gyuho Lee, Amazon Web Services & Sam Batschelet, Red Hat

私達も使ってる etcd の今後の機能に関する紹介とetcd-operatorの紹介です。現在2018年12月の最新はv3.3.10で、来年v3.4のリリースが予定されています。様々な機能が追加されますが、その中の一つとして新しいmember追加に関する課題がありました。etcd clusterに新しいmemberを追加するとLeaderからetcdデータのsnapshotをコピーするという仕組みで動いているのですが、これは脆弱性であるとも言えます。つまり意図しないmemberが追加されると中身が全てコピーされてしまうということです。これを解決するために Learner という役割をRaft(分散合意アルゴリズム)に新たに用意することで、アクセス権限を調整します。詳しくは Learner — etcd documentationにまとめられています。またダウングレード機能も実装される予定です。

etcd-operator は Kuberentes 上で動くOperatorであり、member追加・削除などを自動的に管理してくれるものです。例えば一つのmemberの応答がなくなったらそのPodを削除し、新しいmemberをPodとして参加させて復旧させるなどの機能があります。v1.0では前述のLearnerサポートなどがあります。まだProposalレベルですがPV(Persistent Volume)に対応するアイデアがありました

f:id:cybozuinsideout:20181212161940j:plainf:id:cybozuinsideout:20181212161952j:plain

Kustomize: Deploy Your App with Template Free YAML - Ryan Cox, Lyft

KustomizeはManifest YAMLの支援ツールです。KubernetesのManifestは現実的に定義を書いていくと非常に長くなり、メンテナンス性が悪くなります。それをテンプレート化してパッケージとして扱うものにHelmがありますが、Kustomizeはよりシンプルです。Goのtext/templateによる複雑なテンプレート定義を書く必要はなく、Override形式でManifestを生成することができます。production, development毎に定義を切り分けることもできるし、Podのreplaceが必要かどうか、annotationの付与などが手軽に行なえます。手軽かつ読みやすくManifestが書けるツールは最近欲しいと思い始めたところですが、Helmほど高機能である必要はない。そんな要求にマッチするツールだと感じました。

f:id:cybozuinsideout:20181212162058j:plainf:id:cybozuinsideout:20181212162103j:plain

Becoming Cloud Native Without Starting From Scratch - Marin Jankovski, GitLab

タイトルどおりではなく実際にGitLab社がgitlab.comをKubernetes上にデプロイするためにどのような苦労をしたのかを語っています。弊社のクラウド製品も最終的にKubernetes上に動かすので、その際のヒントになりそうと思いました。自社サービスというものは大抵デプロイの順序が決められており、アプリケーションよりも先にDatabaseのデプロイをしたいです。Kubernetesは宣言的APIですが、依存関係を考えるのは管理者の仕事です。彼らはHelmのみで考えましたが、実際のKuberenetes objectのデプロイ順序はハードコードされているため適合しませんでした。一方でゼロから完全なOperatorを作るのも時間がかかります。最終的な彼らの結論はHelmとOperator両方を使うことで、不満点を補いました。

f:id:cybozuinsideout:20181212162147j:plainf:id:cybozuinsideout:20181212162159j:plain

会場について

シアトルといえばメジャーリーグの球団やAmazon本社があるというイメージがありますが、スターバックス発祥の街でもあり、1号店が海沿いの市場(Pike Place Market)にあります。 今回のような国際会議にはかならずといっていいほど、コーヒーが置かれていますが、今回は全てスターバックスコーヒーです。数カ月分のスタバコーヒーを飲んだような感覚でした。

f:id:cybozuinsideout:20181212162243j:plain

ブースエリアには大企業からスタートアップ企業が自社製品やプロジェクトを展示しています。KubeCon + CloudNativeCon Europeでもありましたが、スタートアップ企業向けの紹介の場が用意されているというのは、彼ら向けのイベントを除いて今までになかったような気がします。回ってみるとかゆいところに手が届くような製品が多く、興味を引きました。またTシャツの配布率が非常に高く、今日一日でなんと15枚も確保できました :D スーツケースの中身はすでにいっぱいです。

f:id:cybozuinsideout:20181212162403j:plainf:id:cybozuinsideout:20181212162359j:plainf:id:cybozuinsideout:20181212162417j:plain

まとめ

KubeCon + CloudNativeCon North America 2018 1日目の参加レポートでした。アメリカ滞在3日目なので時差ボケは大分抜けてきています。明日の参加レポートもお楽しみに!

分散ストレージソフトウェアCephとは

$
0
0

はじめに

こんにちは、Necoプロジェクトのsatです。Necoプロジェクトではサイボウズのクラウド基盤であるcybozu.comのストレージに関する様々な要件を満たすために分散ストレージソフトウェアCephを導入する予定です。本記事ではCephとは何者かについて紹介いたします。Cephが持っている機能は膨大な数にのぼるため、ここでは一部の機能に絞って紹介したいと思います。

概要

Cephはオープンソースの分散ストレージソフトウェアです。2004年以前からカリフォルニア大学で開発され、2006年にオープンソース化されました。現在ではRed Hat社をはじめとするさまざまな企業、あるいは個人によって活発に開発されています。Cephは例えばOpenStack のストレージ機能(Cinder, Swift, やGlanceなど)の選択肢の1つとして使われています。

Cephは複数台のマシン上のストレージデバイスを束ねて一つのストレージプールを作り、そこからPOSIX-like*1なブロックデバイス(RBD)、S3やSwift互換インターフェースを持つオブジェクトストレージ(RADOSGW)、ファイルシステム(CephFS)を切り出してユーザに提供します。

f:id:cybozuinsideout:20181213061217j:plain
ceph

ここで用語をいくつか定義しておくと、CephにおいてはCephクラスタを構成する個々のマシンをノード、その中の個々のストレージデバイス(ないしパーティション)をObject Storage Device(OSD)と呼びます。上図においては円筒型の図形がそれに対応します。

なおNecoではCephFSは使わずにRBDとRADOSGWだけを使う予定なので、これ以降ファイルシステムについては述べません。

データの冗長化(RADOS)

Cephはストレージプール上にReliable Autonomic Distributed Object Store Object(RADOS)というオブジェクトストレージシステム*2を持っており、RBDやRADOSGWなどはRADOSオブジェクトと呼ばれるRADOS上の個々のオブジェクトの塊として提供されます。個々のRADOSオブジェクトは通常複数のレプリカが作成され、それぞれ別のOSDに保存することによってデータの冗長化を実現しています。以下レプリカ数3のRBDを示した図です。

f:id:cybozuinsideout:20181213061221j:plain
rados

データのレプリカを持つことによって個々のOSDに問題が発生したときもデータは失われず、運用が継続できます。それだけではなく、レプリカ数が2なら運用を継続するものの、1になったらそのオブジェクトには冗長度が2以上に回復(冗長度を回復させるリバランス処理については後述)するまでアクセスを失敗させることもできます。

データ配置位置の決定方法(CRUSH)

個々のRADOSオブジェクトのレプリカをどのOSDに配置すべきかはどのようにして決定するのでしょうか。CephにおいてはControlled Replication Under Scalable Hashing(CRUSH)というアルゴリズムを用いて、RADOSオブジェクトの名前のハッシュ値、および現在生存しているOSDのリストをもとにRADOSオブジェクトの配置位置を計算によって求められます。これによってHadoopのNameNodeのような「どのデータをどこに保存すべきか」ということを保存した巨大な管理データを持つ必要がありません。それに加えてI/Oが発生するごとにそのようなデータにアクセスする必要がないので、高速なI/Oが期待できます。

CRUSHはOSDの物理的な位置を考慮してデータを配置するルールを設定できます。具体的には、あるRADOSオブジェクトをすべてのOSDの間でランダムに配置する場合を考えると、すべてのレプリカが同じノード内のOSDに配置されてしまって、1つのノードに問題が発生しただけで当該データにアクセスできなくなるということが考えられます。このような状況を避けるために、Cephでは「オブジェクトは同じノードには1レプリカしか持てない」というルールが決められます。

f:id:cybozuinsideout:20181213063927j:plain
rule

クラスタの管理方法(モニタ)

クラスタの状態は1つないし複数の奇数個のノード上に分散配置されたモニタという機能によって管理します。モニタが複数個存在することによって、たとえば1つのモニタが応答しなくなっただけでクラスタ全体が機能しなくなるというような状況が避けられます。

モニタは「クラスタ内にどのようなOSDがあって、どのOSDにはアクセス可能か」といった情報をまとめたCRUSH mapという(上記NameNodeなどに比べると)小さなデータを持っています。モニタおよびOSDの間では定期的に死活監視処理が動作しており、状況が変わるたびにCRUSH mapは更新されます。モニタ同士はPaxosという合意形成プロトコルを用いて通信し合うことによって、クライアントないしOSDと通信する際には常に最新のCRUSH mapを元に判断ができるようになっています。

クライアントがデータの書き込み要求を出すときは下図のような流れで実際のI/Oが動作します。

f:id:cybozuinsideout:20181213061322j:plain
write

クラスタの拡大

Cephは運用を止めることなく、いつでも新しいOSDをクラスタに登録することによって容量を拡大できます(縮小もできます)。OSDを足した場合にはCRUSH mapが更新され、リバランスと呼ばれるデータの移動が発生することによって、クラスタ内のデータの偏りを防ぎます。

セルフヒーリング

クラスタ内のノードがダウンするなどの理由によってOSDがクラスタから見えないという状態が長時間続くと、Cephは当該OSDはダウンしたものとみなしてCRUSH mapを更新します。ここでリバランス処理が動作して、ダウンしたOSDのデータは他のレプリカのデータをもとに別のOSDに移動することによってオペレータの介在なしに冗長度が自動回復します。ダウンしたノードが再びクラスタが認識すると、データは再びもとのOSDに戻ります

bit-rot耐性

bit-rotという言葉をご存じでしょうか。ストレージデバイス上のデータは、ソフトウェアにバグが無い場合にもデバイスの物理的な問題によってビットが反転してしまうなどの理由によって壊れてしまうことが稀にあります。これがbit-rotです。この問題の恐ろしいところは、何も対策をしていなければデータが破壊したことにユーザは気づかないということです。

Cephに数年前導入されたBlueStoreというストレージバックエンド*3を用いればこの問題を解決できます。BlueStoreにおいてはデータをOSDに保存する際に、データのチェックサムも同時に保存します。OSDからデータを読み出す際に読みだしたデータのチェックサムと保存しておいたチェックサムが一致しない場合はそのデータは壊れているものとみなします。このときレプリカが存在すれば自動的にレプリカから正しいデータを復元させられます。

おわりに

本記事はCephの概要について述べました。今後も本記事では書ききれなかった機能の紹介などについての記事を書く予定です。また、ここではCephの良い面だけ紹介しましたが、世の中に完全なソフトウェアはなく、Cephもまたその例外ではありません。今後Cephを使うにあたって克服しなければいけない課題(たとえば大量のリバランス処理によるネットワークの飽和)、およびNecoチームではそれをどのように解決するかについて紹介する記事を書く予定です。

最後になりますがNecoチームではCephおよび分散ストレージシステムを自動的に構築・管理するシステムの開発者を募集中です。ふるってご応募ください!

キャリア採用 インフラ刷新プロジェクト Neco 開発者 | サイボウズ 採用情報(新卒・キャリア)

www.wantedly.com

*1:POSIX互換ではない部分が一部あります

*2:RADOSはRADOSGWとは異なりS3やSwiftなどとの互換インターフェースは持ちません

*3:データをOSDに保存する際のフォーマット、およびそれを処理するアルゴリズムと考えてもらえればいいです


KubeCon + CloudNativeCon North America 2018 現地レポート 2日目

$
0
0

こんにちは、Necoプロジェクトの森本です。 カンファレンス2日目のレポートです。 0日目・1日目のレポートはこちら。

blog.cybozu.io

blog.cybozu.io

トピックを絞って丸一日議論した0日目Co-Located Event、全参加者を迎えての基調講演から始まった1日目に続き、2日目となります。 日程の組み方が、1日目はビギナー向けを多めとし、そこから次第にマニアックな内容に向かう構成となっているようです。 どんなディープな議論となるか、楽しみ楽しみ。

f:id:cybozuinsideout:20181213155915j:plain
基調講演の会場。とにかく大人数。

Keynote: Kubernetes Project Update

本日も基調講演から始まります。 まずはKubernetesの普及度合いの説明から。 ここまでにInnovatorsからEarly Adoptersへと順調に普及が進み、今はEarly Majorityに手を伸ばしています。 これまでは新機能の追加でワイワイやっていたけれども、ここからの段階では安定して動くこと・一貫した動作をすることが重視されるようになります。 ちょっと退屈なフェーズに入ったように見えるかもしれませんが、ここからが大切なところ。 これからもOpen StandardとExtensibilityという二本の柱を守って進めていきましょう、という呼びかけでした。 Kubernetesの利用者に対しては、システムが大きく変わってしまってそれまでの投資が無駄になる、なんてことはないんだよと安心を与えるような呼びかけとなっていました。

Keynote: Save Yourselves!

こちらのお話はセキュリティに関してのもので、発表者は会議の司会進行役も務めるLiz Riceさん。 この人が登壇するだけで会場から歓声が上がるという人気者です。 まずは基本的な話として、そこらに転がっているようなマニフェスト(サービスの構成情報)を適当に使っちゃうと大変なことになるよ、ということで実演してみせます。 ネットから落としたYAMLファイルをkubectl applyしてデモの画面に表示されたのは……"all your pod are belong to us"。 これは冗談としても、野良マニフェストを使うとadmin権限でやりたい放題の状態となってしまうので注意が必要です。 やりたい放題を防ぐ仕組みとして、APIサーバまでの間にAdmission Controllerを挟んで「このリソースにこの操作はやっていい、この操作はやっては駄目」等々細かく判断させる、あるいはそれをもっと簡便にOpen Policy Agentのポリシーとして記載する、といった対策が紹介されていました。 最近だと不正に得た計算パワーを仮想通貨のマイニングに悪用するというのが定番らしく、気を付けたいところですね。

Keynote: Developing Kubernetes Services at Airbnb Scale

さらに基調講演から、近来話題となっているAirbnb社さんにおいてmicroservicesをどう取り扱っているかというお話です。 サービスデリバリのサイクルを細かく速く回してサービスを提供したい、モノリスなサービスは大規模化が進んでいてまったくデプロイを加速できない、ということでKubernetesでのmicroservicesという形でサービス提供を進めているとのことです。 しかし1,000を超えるチーム(すごい規模ですね)がそれぞれmicroservicesを取り扱おうとすると、効率化を進めなければいけません。 まずKubernetesで問題となるのは、使い始めたときに誰もが感じることかと思いますが、いくつもいくつもYAMLファイルが必要となること。 1つのサービスのために10個以上のファイルを用意し、しかもどのサービスでも似たような内容となるファイルを毎回用意する必要があります。 Kubernetes boilerplate! (決まり文句!)でウンザリだということで、これをテンプレートから生成するようにします。 さらに、サービスごとにenvironment/namespaceを区切るわけですが、区切った際の名付け方を標準化しておきます。 これにより、同じことを何度も書くという無駄な手間を省き、どの環境でも標準化された方法で操作対象を指定できるようになります。 このほか、CI/CDを簡単にできるように、特に開発者がそれぞれでできるように仕組みを用意する等、実際に開発・運用していて詰まったんだろうなというところを丹念に潰しているように思えました。 開発者の試行錯誤が見えてくるような発表でした。

前夜の爆弾

ここまで基調講演を見てきましたが、実は前夜に爆弾が投下されていたのでした。

身も蓋もないと言いますか何と言いますか。 これを受けてかどうか、今日の基調講演はそう悪くなかったと思うのです。 徹夜で必死に直した方もいたのでしょうか……。

f:id:cybozuinsideout:20181213160946j:plain
唐突なお土産コーナー。各プロジェクトのマスコット人形を売っていたりします。

Building Container Images on Your Kubernetes Cluster with Knative Build

この後は各部屋に分かれてのセッションです。 CIのためにビルドシステムを別途用意するのは手間だよね、いつものKubernetesクラスタの中でビルドを扱いたいよね、ということで、Knative Buildによりビルド手順を表現し、Kubernetesクラスタで実行するというものです。 Knative (ケイネイティブと読まれていました)はGoogleが推進しているプラットフォームで、サーバレスなアプリを手軽に書いて、後のこと(ビルドだのデプロイだのモニタリングだの)はお任せというものです。 ここでは、CRD (Custom Resource Definition; 拡張APIのためのデータ構造)を用いてビルドプロセスをモデル化し、ビルド手順のステップを「このコンテナイメージでこのコマンド(ビルドだったりテストだったり)を実行する」という形で記述して与えていました。 私たちNecoプロジェクトでもCircleCIの設定でそのようなビルドステップを記述していますが、より統一的・汎用的に扱えるようになるのでしょう。

So You Want to Run Vault in Kubernetes?

HashiCorp社さんが公開している機密管理ツールVaultを、Kubernetesクラスタで安全に動かすにはどうするかという、ベストプラクティスの発表です。 Necoプロジェクトでもちょうど、Kubernetesクラスタの構築の中でVaultをセットアップして管理基盤に据えています。 さて自分たちのやり方でおかしなところはなかったかなと発表を見に行きました。 その結果ですが、API利用は必ずTLSを用いましょうとか、ロードバランシングはL7ではなくL4としてTLSの終端はVaultが握るようにしましょうとか、IPC_LOCKを指定してページアウトを防ぎましょうとか、まったくもってそのとおり、私たちもそのように気を付けてるよ、というものでした。 期待以上でも期待外れでもなく、期待どおりというところ。 まあ、それで良かったのでしょうね。

プロジェクトメンバーで振り返ったところ、実はこれ以外のセッションでも似たような問題(期待以上ではない)が発生していました。 多くの参加者を対象としたセッションではそこまで深い情報は出てこないため、自分たちがこれまでに分け入ったコンポーネントについてはあまり新たな情報は得られないのでした。 各コンポーネントのメンテナの方とお話しできるよ、という時間帯も設けられていますので、深く突っ込みたい場合はそこに突撃するのが良いのでしょう。 相当のコミュニケーション能力、というか英語力が試されますが……。

f:id:cybozuinsideout:20181213161401j:plain
求職・求人コーナー。定番の光景です。

All Attendee Party!!!

夕方になりました、なんだかみんなソワソワし始めました。 本日は締めの基調講演はなく、会場から場所を移してパーティーなのです! シアトル名物のSpace Needleタワーに上ったり、美術館や博物館が立ち並ぶエリアを散策したり、楽しみですね!

というところなのですが、残念、これもシアトル名物の冬の雨に降られてしまいました……。 今日は朝から青空も見えていて珍しく落ち着いた天気だったのですが、狙いすましたかのような雨です。 しかも8,000人がシャトルバスに乗るための行列。 人、人、人。

結局パーティーは諦め、近くのレストランでおいしいものを食べて退散しましたとさ。 めでたしめでたし。

ブログの最後は素敵な写真で飾ろうと思っていたのですがそれもおじゃんとなりました。 明日 3日目の現地レポートをもって、埋め合わせとさせていただきたく思います。 どうかお楽しみに。

QAエンジニアのAgile Testing vol.3

$
0
0

こんにちは!松山品質保証部の北地です。社内で「セキュリティテスト」をテーマにパネルディスカッションを開催しました。その様子と得られた気づきをご紹介します。 f:id:cybozuinsideout:20181205170314j:plain

テーマ選定やイベント開催の経緯についてはこちらにまとめています。

QAエンジニア(以下、QA)をメインターゲットとして企画した本イベントですが、プログラマー(以下、PG)やスクラムマスター、プロダクトオーナーにも興味を持ってもらえて、総勢約40名が集まりました!

パネラーに投げかけた質問

パネラーにはサイボウズ製品の品質保証責任者とセキュリティテストを担当するPSIRT(Product Security Incident Response Team の略)を招集しました。パネラーに向けて以下のような質問を投げかけディスカッションしていただきました。(一部抜粋)

  • セキュリティテストの実施単位は?
  • PSIRTは試験計画をどのように立案しているの?
  • 開発チームはいつごろ、どのような内容でPSIRTに検証を依頼しているの?
  • 社外から脆弱性の報告があった場合、対応フローは?
  • PSIRTは開発チームのスクラムイベントに参加している?
  • PSIRTが開発チームのスクラムイベントに参加する必要はある?

f:id:cybozuinsideout:20181113153705j:plain
東京でのパネルディスカッションの様子

ディスカッションにより気づいたこと

上記のような質問に対してディスカッションを進めることで以下の気づきがありました。

  • セキュリティテストの実施タイミング
    "開発完了後に一括して実施"するチームと"スプリント単位で実施"するチームがありました。
    前者は、PGとPSIRTの間をQAが仲介しており、 変更が発生した際のPSIRTとのやり取りの手間がかかるために、"スプリント単位での実施"は困難とのことでした。
    一方、後者は、PGとPSIRTメンバーが直にやり取りをすることで効率化しているようでした。

  • PSIRTにとっての、スクラムイベントに参加するメリット
    要件を早めにキャッチアップできたり、PGとのコミュニケーションがスムーズになったという意見が多かったです。

  • 振り返り
    PSIRTは内部で振り返りを実施しており、開発チームの振り返りには参加していませんでした。しかし、このディスカッションを通して開発チームの振り返りに PSIRTが参加することのメリットはありそうだという意見が出ました。

  • 設計段階からの参加
    今後の課題として、PSIRTがリリース前にセキュリティ観点で品質を作り込めることが挙げられました。

f:id:cybozuinsideout:20181205170317j:plain
松山オフィスからのリモート参加の様子

イベントを振り返ってみて

SPITzメンバー及びパネラーを交えて振り返りを行いました。Agile Testing活動のコンセプト(※)を実感できたか、という点を振り返ってもらった結果、「少し実感できた/あまり実感できなかった」という意見が大半でした(以下、参加者のコメント)。

〇少し実感できた

  • 各チームのセキュリティテストのプロセスの流れはほぼ同じだったが、ほかのチームの取り組み方について知ることはできて、興味深かった。
  • 今まで知らなかった他製品のプロセスを知れたことが単純に面白かった(参考になった)。

〇あまり実感できなかった

  • 今すぐ何か取り入れたいということはなかった。
  • 自分たちのチームではやっていない取り組みの話題は出ていたが、具体的な話をその場で質問できなかったため、壇上のメンバー同士で質問をする時間をもうけてもよかったかもしれない。

総じて「他製品のチームのプロセスへの理解が深まったものの、次のアクションにつなげるための効果としては弱かった」といった印象です。次回の活動では今回の弱点を改善していきます!

(※)製品QAメンバーに「他のチームのAgile Testing情報参考になる。自分たちもこれから取り入れてみよう!」と言ってもらうこと。

次回予告

「理想のAgile Testing ワークショップ 」を開催する予定です。レポートにご期待ください!

We are hiring!!

サイボウズでは「日々の仕事をカイゼンしたい」「いろんな人と繋がって学び合いたい」というカイゼン意欲をもった方がチャレンジできる環境があります。

キャリア採用 QAエンジニア | サイボウズ 採用情報(新卒・キャリア)
キャリア採用 QAエンジニア(ミドルウェア) | サイボウズ 採用情報(新卒・キャリア)
キャリア採用 テストエンジニア | サイボウズ 採用情報(新卒・キャリア)

ぜひ私たちと一緒に働きましょう!

KubeCon + CloudNativeCon North America 2018 現地レポート 3日目

$
0
0

こんにちは、Necoプロジェクトのsatです。カンファレンス最終日、3日目のレポートです。

0日目から2日目のレポートはこちらです。

blog.cybozu.io

blog.cybozu.io

blog.cybozu.io

2日目までは執筆者各人がその日に参加したセッションの中からいくつか面白かったものを選んで書くというスタイルでしたが、最終日の今日は少し毛色を変えて1日目と3日目に合計4つのセッションがあったRookというソフトウェアを集中的に紹介したいと思います。Rookとは分散ストレージソフトウェアの一つであるCephをkubernetesによって管理するためのオーケストレーターです。

Cephについてご存じないかたは、本記事を見る前にCephの概要について述べた以下の記事をごらんください。

blog.cybozu.io

Intro: Rook - Jared Watts, Upbound

これはRookの基礎について紹介するセッションでした。

Rookは前述の通りkubernetesを使ってCephを管理するソフトウェアです。RookはまたCNCFのincubating level(孵化段階の)プロジェクトでもあります。

github.com

kubernetesはボリュームプラグインという仕組みによってアプリケーションに提供する永続ストレージのプロバイダを追加できます。Cephもそのバックエンドの一つとして利用できます。ただし、そのためにはユーザが自分自身でCephクラスタを構築、管理する必要があります。kubernetesを拡張してこれらの面倒事を自動管理してくれるソフトウェアがRookです。

f:id:cybozuinsideout:20181214141409j:plain

Rookが提供する機能は単なるクラスタを自動構築、管理だけではありません。Cephは通常クラスタを管理するためにユーザがコマンドを発行する必要があります。これに対してRookではkubernetesが扱う各種リソースと同様にCephクラスタを宣言的に扱えます。例えばCephクラスタを定義するyamlファイルにモニタの数を3つと書いておけば、モニタの数が3つであるクラスタを自動的に構築し、かつ、モニタのうちの一つがダウンしたとしても自動的にモニタの数が3に戻るようにRookがよきにはからってくれます。この宣言的な管理を自前で実装するのではなくkubernetesに任せるというのがRookの巧みなところです。

f:id:cybozuinsideout:20181214141413j:plain

セッションの後半にはRookを使って実際にCephクラスタを構築するデモがありました。Cephのクラスタを幾度となく構築した経験がある筆者から見ると、「なんて楽なんだ」というのが率直な感想です。kubernetesに短いyamlファイルを投入するだけでモニタやOSDなどのCephの各コンポーネントがPodとして次々に立ち上り、次第にCephクラスタが出来上がってゆく様は圧巻でした。

Deep Dive: Rook - Travis Nielsen, Red Hat

このセッションは前述の"Intro: Rook"に比べてRookのさらに詳細について扱うものでした。

前半はRookにおけるCephの各種リソースの定義についての話でした。ここではそのうちの一部について紹介いたします。

  • Cephクラスタを示すリソース: 指定されたバージョンのCephを使ってクラスタを作る。この定義を変更すればCephを自動的にアップグレードしてくれる。Rookのバージョンも同様に管理できる
  • OSDを示すリソース: どのノードのどのストレージデバイスをOSDとして提供するかを定義できる。それに加えてストレージデバイスが並列アクセスが高速なNVMe SSDだった場合に、デバイスごとに複数のOSDを定義してI/Oを高速化させられる

今後もいろいろ定義できる項目が増えていくことでしょう。

後半にはいくつかのデモがありました。たとえばRookによってCephの一つ前の安定版(コードネームはLuminous)から最新の安定版(コードネームはmimic)にアップグレードするといったものがありました。クラスタを定義するyamlファイルの中のCephのバージョンに相当する部分を一か所書き換えるだけでアップグレードができるというのは筆者にとっては驚きでした。

Adding a New Storage Provider to Rook - Jared Watts, Upbound

このセッションはRookに新しいストレージプロバイダを追加する方法についてのものでした。

ここまでCephの話ばかりしていましたが、実はRookはCeph以外のストレージソフトウェアも永続ストレージのプロバイダとして使えるようにするためのフレームワークを持っています。すでに現在NFS, Cassandra, CockroachDBなどをサポートしています。ソースコードを見たところ、現在のRookの主たるプロバイダはあくまでCephであり、残りのストレージソフトウェアは一部の機能しか実装していない評価版のような位置づけのようです。たとえばストレージプロバイダのAPIのバージョンはCephが先月末に唯一最初の安定版といえるv1になった以外はalpha版扱いです。

本セッションでは既に取り込まれているMinioのプロバイダを例に、プロバイダを追加するにはkubernetesにどのようなリソースを定義してどのようなコードを書けばいいかということを紹介していました。個人的にはCeph以外のプロバイダを使うことは無いと思いますが、開発全体が活発で、かつ、個々のプロバイダがRook本体と独立して開発できるのはいいことだと思います。

Meet the Maintainer: Rook - Jared Watts, Upbound

これはRookに興味のある参加者がRookのメンテナに直接質問できるセッションです。

筆者は本イベントに参加する前にRookについて興味を持っていたため、その動向を追ってきました。それに加えて各種Rookのセッションを見たことによって、いくつかの疑問が湧きました。せっかくなので本セッションにおいてそれらについてメンテナに質問してみました。

  • Q: 現在RookはRADOSオブジェクトのレプリカをそれぞれ同じノードに置かないように設定できるが、それ以外の設定はできない。私が作ろうとしているCephクラスタにおいては各レプリカを別ラックに置きたいと思っている。直近でこのような設定をできるようにする予定はあるか
  • A: 今後しばらくはRookによってクラスタを組んだ後にRookを介さずに直接Cephを操作することによってそのようなことを実現するしかない。将来的にはサポートするかもしれない。

  • Q: 最新版のv0.9までは毎回のように非互換が発生していた。これではプロダクション環境でRookを使いにくい。今後しばらくはこのような状況が続くと考えてよいか

  • A: 今後は非互換を発生させないようにする見込み。もちろんRookに閉じた話ではなくCephに非互換が発生した場合はこの限りではない

  • Q: Cephには大量のリバランス処理をきっかけとしてネットワークの帯域を使い果たし、一切クラスタにアクセスできなくなるという問題が起こりうる。実際そのようなケースをこれまでにいくつか見てきた。この問題に対処するための帯域制御機能をRookに実装する予定はあるか

  • A: 今のところはそのような帯域制御はユーザ自身がするしかない。Rookがそのような仕組みを用意することはやろうと思えばできるだろうが、そのような機能を追加する予定は今のところ無い

知りたいことにすべて丁寧に回答してもらえたので非常に満足できたセッションでした。

ここで余談をひとつ。本イベントは多くのセッションについてスライドが公開されていたり、ものによっては発表時の動画が公開されることすらあります。それでも現地に行く価値はどこにあるのでしょうか。それは上記のような質疑を含む参加者同士の対話です。みなさまもこの手のイベントに参加する際はご自身の持つ課題についてメンテナを含む他の参加者と議論してみてください。イベントがもっと楽しくなりますよ。

Rookについての現在の印象

Rookは宣言的にCephクラスタを扱えること、および、そのために既にある程度成熟しているkubernetesを利用しているという2つの点において、なかなか筋がいいソフトウェアに見えます。現在はまだ粗削りですが、Red Hat社やQuantum社などの企業のエンジニアやホビープログラマなどの多様な人々によって活発に開発が進んでいることより、将来的には「kubernetesでCephを使うのならばRookを使うのが常識」という状況になっても不思議ではありません。今後も引き続き開発動向を追っていく予定です。

KubeCon全体の総評

とにかく人数が多くて熱のこもったイベントだったというのが全体の印象です。筆者はこれまでOpen Source SummitなどのOSSに関する国際イベントにいくつも出てきましたが、八千人もの大人数が参加する規模ものはかつて無かったです。世間のコンテナ熱、kubernetes熱を見るに、次回以降もますます盛り上がっていくことでしょう。

もちろん我々は単に驚いて圧倒されていたわけではなく、得るものがたくさんありました。ここではそのうちの1つを紹介いたします。我々はkubernetesの最新、かつ深い知識を得るために本イベントにやってきました。ところが、幸か不幸かレベルが高いと記載されているセッションに参加しても既に知っている内容が多かったです。このため、次回以降は参加するだけではなく、自分たちのセッションを持つことによって我々の知見をkubernetesのコミュニティに共有することが一つの目標になりました。

これまで4日にわたってKubeCon + CloudNativeCon North America 2018の様子をお届けしてまいりましたが、これにてすべて終了です。ここまで読んでくださった方に感謝いたします。我々のレポートがみなさんにとって何かしら役に立つものであれば幸いです。

社内ハッカソンを開催しました

$
0
0

f:id:cybozuinsideout:20181102154637j:plain

こんにちは!開発部の刈川です。 サイボウズでは毎年社内でハッカソンを開催しているのですが、先日開催したので簡単に内容をご紹介したいと思います!

概要

f:id:cybozuinsideout:20181102155726p:plain

今年は東京、松山、大阪、ベトナムの4拠点と各々の自宅(在宅勤務)を含めた55名の方に参加してもらいました。 期間は3日間でテーマは自由となっています。3日目に最終発表会を行い、その中から投票で4つの賞を決めました。 賞は以下のとおりです。

  • 大賞
  • 技術(チャレンジ)賞
  • アイデア賞
  • デザイン賞

成果物をいくつかご紹介します。

Terraform-provider-kintone(大賞)

大賞はTerraform-provider-kintoneでした。これはTerraformというインフラ構成管理ツールのkintone用のプラグインです。 嬉しいポイントをざっくりいうと『いままで画面をポチポチやって作るしかなかったkintoneのアプリケーションをコードベースで管理/デプロイすることができる』ということです。kintoneもterraformも知らない方には「?」という感じですが、これができるようになると様々なことが自動化できて、kintoneが更に便利になるのです。 www.terraform.io

ぷよぷよのプレイ動画を解析して棋譜を生成(技術賞)

f:id:cybozuinsideout:20181130163241p:plain
©SEGA
その名の通り、ぷよぷよのプレイ動画から棋譜を生成して分析するためのツールです。 予め用意したパターン画像からぷよの画像の差分をとって色を判定したり、ツモった時や連鎖が発生したタイミングをグラフで分析したりと技術の無駄遣い感がものすごいのですが、いかにもハッカソンらしいチャレンジが評価されました。
f:id:cybozuinsideout:20181130163731p:plain
ぷよぷよってこんなゲームなの…?

kintone-VR

kintone-VRは、Oculus Goとkintoneを連携してVR空間にkintone上のコメントを描画するシステムです。 ハッカソンの成果発表では実況スレッドを利用しており、デモでは実際にこのシステムを見た反響のコメントがVR空間に描画されていました。

Oculus Goでわいわい

開催にあたって

社内ハッカソンは毎年開催しているのですが、年ごとにテーマを設けています。 たとえば「コネクト」がテーマの回では、普段の業務では関わりのない人同士を運営側でランダムにチーム分けしてハッカソンに臨んでもらいました。こうすることで今まで知らなかったノウハウの共有や、その人がどういう技術が得意だということが分かったりと、ハッカソンの成果物以外の副産物としてのメリットも生まれます。

参加者同士でランチ        在宅でハッカソンしてる人

おわりに

最近サイボウズではモブプログラミングが流行しているので、今度は合宿形式×モブプログラミングで企画をする予定です。 開催したらこちらでご紹介したいと思います。それではまた!

Kubernetesのネットワークプラグインを自作した話

$
0
0

こんにちは、インフラ刷新プロジェクト「Neco」の@ueokandeです。 本日はインフラ刷新プロジェクトで開発したKubernetesネットワークプラグインについてお話します。 Necoでは現在のアーキテクチャを刷新して、機材管理やアプリケーションのデプロイを簡単にするために、オンプレミス環境でKuberneteを運用できる仕組みづくりに取り組んでます。 その成果物の1つである、Kubernetesネットワークプラグイン「Coil」についてご紹介します。

github.com

Kubernetesは管理者がプラグインを選択・作成することで、実行環境や要件に合わせて自由にカスタマイズできます。 ネットワークプラグインもコンテナの実行環境に合わせてカスタマイズ可能です。 サイボウズが自社データセンターでKubernetesを運用するために、Kubernetesネットワークプラグインを作成しました。 この記事では、Coilのアーキテクチャと、なぜ自社開発に至ったかの経緯をお話しします。

Kubernetesのネットワークの仕組み

コンテナを docker runコマンドで起動するとき、DockerはコンテナにDockerのプライベートネットワークからIPアドレスを割り当てます。 このネットワークはノード上に閉じたネットワークで、異なるノード上のコンテナ間は通信できません。 そのためPodが適切なネットワーク設定やIPアドレスを利用できるよう、Kubernetes管理者は環境に応じて適切なネットワークを設定します。

Kubernetesのネットワークを設定する仕組みは、ネットワークプラグインという形で実現されます。 Kubernetes管理者は適切なネットワークプラグインを各ノードにインストールして、kubeletがそれを利用するように設定します。 ネットワークプラグインはContainer Network Interface (CNI)という仕様に基づいており、CNIプラグインとも呼ばれています。

CNIプラグインは実行可能なバイナリです。 環境変数や標準入出力からコンテナの情報を受け取り、結果を標準出力へ返します。 Podがデプロイされてkubeletがコンテナを起動するとき、CNIプラグインを実行します。 CNIプラグインは渡されるコンテナIDやネットワーク名前空間をもとに、適切なネットワーク設定を適用したり、割り当てるIPアドレスを返します。

なぜネットワークプラグインを開発したか

Kubernetesのネットワークは、性能やスケーラビリティを考慮して、各Podが持つIPアドレスはBGPで経路制御することが多いです。 新しいインフラでも、各ノードやPodはすべてBGPによって経路を制御します。 ネットワーク設計やノード側の設定は以下の記事からどうぞ。

BGPが扱えるCNIプラグインはCalicoRomanaなどすでにいくつか存在します。 特にCalicoは多くの企業で採用実績があります。 これらのプラグイン導入も視野に入れてましたが、調査してみて採用に至りませんでした。 これらのプラグインはBGPスピーカーを組み込んでいます。 そしてNecoのネットワークでは、それぞれのノードも自身のIPアドレスを広告してます。 この2つを併用するとき、BGPでは問題が発生します。

それぞれのBGPスピーカーが利用しているBFDという障害検知の仕組みは、隣接するネットワーク機器とピアリングします。 現状それぞれのノードがToRスイッチとピアリングしているため、CNIプラグインがToRスイッチとピアリングできません。 ノード側とCNIプラグインそれぞれの経路を広告するには、片方のBGPスピーカーが両方の経路を広告するような実装が必要です。 これをCalicoまたはRomanaで実装するのは、メンテナンス性などの理由で将来性がないので、最終的にCNIプラグインを開発するという結果に至りました。

Coilの設計方針

CoilがやることはコンテナのIPアドレス管理とネットワークへ経路を適用するのみです。 CNIプラグインは「プラグインチェーン」という仕組みがあり、複数のプラグインを組合せることができます。 ネットワークポリシーの設定などはCoilはやらず、CalicoやCiliumなど他のプラグインを利用する予定です。

Coilの大きな特徴の1つは、BGPスピーカーを組み込まないという点にあります。 Unix哲学には「1つのプログラムは、1つのことをうまくやる」という言葉あります。 Coilはコンテナのアドレスをネットワークに広告せず、別のソフトウェアが経路広告できるように経路情報を外部に出力するだけです。 Necoのネットワークではすでに各ノードにBIRDがインストールされており、ノードのIPアドレスをネットワークに広告しています。 Coilは自身が経路広告せずに、各ノードにインストールされているBIRDがコンテナのアドレスをネットワークに広告できるようにしました。

Coilのもう1つの大きな方針は、アドレス単位の経路広告ではなく、サブネット単位の経路広告という点です。 これはRomanaの設計からインスパイアされました。 CalicoはPodのアドレス広告を/32のアドレスとして広告します。 つまりPodの数だけ経路情報がネットワークに広告されます。 Necoでは将来数十万のPodをデプロイすることを想定してるため、パフォーマンス面で心配がありました。 一方Coilは各Podが持つアドレス単位ではなく、ひとかたまりのサブネット(/27など)として経路広告します。 この単位をCoilではブロックと呼んでいます。 経路広告をブロック単位にすることで、ネットワーク全体の経路数が大きく減ります。 たとえばブロックのサブネットマスクが/27とすると、アドレス単位の経路広告と比べて広告する経路数は1/32に抑えることができます。

ブロックの確保はノードに対して割り当てられ、Coilはノードに割り当てられたブロックからIPアドレスをPodに割り当てます。 Coilはブロックの経路広告と、ノード内のPodの2つの経路を設定します。 前者の経路はネットワークに広告され、どのPodのアドレス(のサブネット)をどのノードが持ってるかを知ることができます。 後者はノード内からPodに到達するための経路です。 この2つの経路を設定するとこで、異なるノードにデプロイされたコンテナの経路を知ることができます。

Coilのアーキテクチャ

BIRDがどの経路を広告すべきかを見れるよう、Coilは割り当てられたブロックを指定されたLinuxのルーティングテーブルに出力します。 Linuxカーネルはシステムが利用するルーティングテーブルのほかに、複数のルーティングテーブルを持っています。 これはLinuxカーネルがネットワーク経路に使用しないので、通常のアプリケーションは影響を受けません。 BIRDはこのルーティングテーブルから経路情報を参照し、ネットワーク全体に広告します。 つまりBIRDに限らずこのルーティングテーブルを参照できるなら、quaggaなどの他のソフトウェアと組合せて利用できるようになっています。

Coilのアーキテクチャの図が以下のようになります。ブロックやIPアドレスの割り当て情報はetcdに保存します。 各ノードはCNIプラグインの coilと、coilのデーモン coildがデプロイされます。 CNIプラグイン coilは、kubeletから呼び出して coildに問い合わせるのみです。 ブロックやアドレスの割り当てをするのは coildです。 coildはetcdを参照して、新しいIPアドレスをコンテナに割り当てたり、ブロックが必要なら新たにブロックをノードに割り当てます。 新しいブロックを割り当てたら、それをBGPによって経路広告するためにルーティングテーブルに出力します。

f:id:cybozuinsideout:20181218164058p:plain
Coilのアーキテクチャ

各Podに経路を割り当てるまでのフローは以下の通りです。

  1. Podがデプロイされると、KubeletがCNIプラグインのcoilを呼び出します。
  2. coilcoildにPodの名前空間を渡して、新しいIPアドレスを要求します。
  3. coildは現在のブロック割り当て情報から、利用可能なIPアドレスを探します。 もし割り当てたブロック内で利用可能なアドレスがなければ、新たなブロックを割り当てます。
    • 新たなブロックを割り当てたら、そのブロックをルーティングテーブルに登録します。
    • BIRDは新しいブロックがルーティングテーブルに追加されたら、ネットワークに経路広告します
  4. 割り当て可能なIPアドレスがあると coildcoilに結果を返します。
  5. coilはコンテナのIPアドレスとノード内の経路を設定します。

Coilはアドレスプールと、Podが属するKubernetes名前空間を紐づけます。 Kubernetesの名前空間の認可を設定することで、特定の権限があるユーザのみに、特定のIPアドレスを割り当てるよう設定できます。 たとえばサイボウズでは利用者全員がグローバルIPを利用できないよう、プライベートIPとグローバルIPを別の名前空間に割り当てます。 そして特定の権限があるユーザーのみ、グローバルIPアドレスを持つPodをデプロイできます。

まとめ

Coilの開発状況は、機能は一通り実装が完了しており、これから本番環境に投入予定です。 ネットワークプラグインの開発は新しいチャレンジでしたが、すでにチーム内ではBGPネットワークの知見が溜まってたのでスムーズに開発が進みました。 またCoilの責務を最小限にして、多くの機能を実装しなかったのがカギです。 おかげでBIRD以外のルーティングソフトウェアからも利用できる設計になりました。

サイボウズでは自社でKubernetesを運用して、そのうえでサイボウズのプロダクトを運用できる環境を開発しています。 他にもまだまだ紹介してないソフトウェアがあるのですが、それはまた別の機会に紹介したいと思います。

OpenSTFとkintoneでモバイル端末を管理する話

$
0
0

こんにちは。品質保証部の園です。 モバイル端末、何台持っていますか? 管理面倒ですよね!!
積年の問題を解決すべく、OpenSTFkintoneを組み合わせて、検証用のモバイル端末数十機を管理する仕組みを作りました。はまりどころ含めてご紹介します。

OpenSTFの導入

弊社が抱えていた問題点

製品の試験に使う端末を物理的に貸し出していました。その数ざっと100台ほど。iPhone、iPad、各種Android端末、フィーチャーフォンなどなど、OSのバージョンや画面サイズが異なるものが大量にあります。これらはkintoneで作成した「モバイル端末管理アプリ」を用いて、目当ての端末の利用状況を確認し、運よく空いていれば利用登録、管理棚から持ち出して作業する。という流れです。

この管理方法では、以下のような問題が発生していました。

  • アプリ上は利用されていないはずの端末が管理棚に存在しない
  • いつの間にか破損・紛失している
  • 試験をしたい社員が勤務しているオフィスに、目的の端末がない

最初のふたつだけでも面倒なのですが、複数の拠点をまたいでいるプロジェクトでは3番目の問題も厄介でした。エミュレータで済ませられる場合にはそうしていたのですが、どうしても実機での検証が必要な場合、新たに端末を購入したり、海外拠点へ持っていったりしていました。私自身、ベトナムや上海への出張時に試験用の端末を持参したことが度々あります。

OpenSTFとは?

CyberAgent社が作成したモバイル端末管理用ソフトです。現在、OSSとして開発が進められています。

メインの機能は「モバイル端末の管理」と「接続されているモバイル端末の遠隔操作」です。 例えばMicrosoft SurfaceのようにタッチセンサーのついたPCから使用することで、Webブラウザ上に表示されているモバイル端末を指で操作することができるため、実機に近い感覚で操作できます。

OpenSTFの導入効果

OpenSTFを導入することで、前述の問題がほぼ解決できました。 利用者側からは「端末をロッカーの中から探す手間が省けた」という喜びの声があがった他、PCからの操作(「キーボードが使える」「試験仕様書からのコピペが可能」)が好評でした。 定期的に端末の棚卸(資産管理をするため)を行っていた管理責任者も、手間が減って喜んでくれました。端末を専用のラックにいれてOpenSTFに接続したので、行方不明な端末の捜索や破損事案が減ったのです。

一方、海外拠点からの利用についても、速度的な不満は出ませんでした。私も上海オフィスやベトナムオフィスに出張した際に、東京オフィスにある端末を操作してみましたが、特別遅延は感じられませんでした。 ただし、この感想はあくまで手動でテストした際の感想です。 Seleniumなどを使って自動でテストをする場合は、Selenium(Node)をOpenSTFを設置した拠点に配置するなどして、なるべく遅延をなくす工夫をするべきでしょう。

OpenSTFの導入にともなって実施したこと

このようにOpenSTFの導入効果は高かったのですが、運用開始にたどり着くまでには、いくつかの問題に対処する必要がありました。

f:id:cybozuinsideout:20181218152854p:plain
システム構成図

LDAP認証の設定

新しいシステムを作る度に新しいユーザー・パスワードの作成を求めていては、ユーザーの利便性を損ない、システムの利用が促進されません。
…というのは半分建前で、ユーザー管理は手間が増えるので、やりたくありません。そこで、OpenSTFのユーザー認証を社内のActive Directoryに連携させることにしました。

LDAP認証を利用するために、OpenSTFの認証サーバーを以下のように設定します。

stf auth-ldap --app-url https://${PUBLIC_IP}/
--port 3000
-t 60
--ldap-url ldap://${LDAP Server}:${LDAP Port}
--ldap-bind-dn "cn=${CN},ou=${OU},dc=${DC},dc=${DC}"
--ldap-bind-credentials ${AD_PWD}
--ldap-search-dn "dc=${Search-DC}"
--ldap-search-field "cn"
--ldap-username-field

通信のSSL化

作成したOpenSTF環境への接続は、社内ネットワークからに限定しています。 とはいえLDAP認証を使用しており、パスワードが暗号化していない経路を通るのは望ましくありません。 以下の2点を実施して、通信をSSL化しました。

  • OpenSTFが使うNginxのSSL化
  • OpenSTFの起動時に付与するオプションを、HTTPS通信、WSS通信に変更

stf app --auth-url http://${PUBLIC_IP}/auth/ldap/
--websocket-url ws://${PUBLIC_IP}/
--port 3000
 ↓ 以下のように変更 ↓
stf app --auth-url https://${PUBLIC_IP}/auth/ldap/
--websocket-url wss://${PUBLIC_IP}/
--port 3000

端末管理用のラック関連

モバイル端末は専用のラックに収納し、施錠して管理することにしました。

f:id:cybozuinsideout:20181218151242j:plain
ラックに格納された端末

ラック内にはハブを配置し、ケーブルに繋がれた端末が数十台鎮座しています。一時期ニュースをにぎわしていたような、過充電による爆発炎上は無いと思いますが、機材の異変は素早く察知し、対処したいものです。 異常発熱への対策として、端末/ラック監視を強化し、充電しない時間帯を設けることとしました。

端末の監視

OpenSTFは、端末のバッテリー温度を常に取得しています。
この情報は、OpenSTFのAPIにより外部プログラムから取得可能なので、端末の温度の急激な変化を察知して通知するようなプログラムを構築することが可能です。

ラック全体の温度監視

運用の都合上、モバイル端末が格納されたラックは、サーバールームではなく、執務スペースに配置されています。ラック自体が閉塞された環境になりやすい構造のため、オフィスの暖房と合わせて温度が上昇してしまうことを懸念しました。対策として、USBファンによる送風や、USB温度計による監視を実施しています。

f:id:cybozuinsideout:20181218151340j:plain
ラック背面側
USBファンはPCケース用の120mmを4つ配置。このあたりは静音PCを組んだ経験が生きています。

充電時間の調整

とはいえ、監視していても24時間体制でトラブルに即対処できるわけではありません。弊社の場合、実機での作業は人間による確認作業の割合が高いため、業務時間外の利用はほとんどありません。そこで、稼働がなく対処できない時間帯については端末の充電を止め、トラブルの要因を減らす方針にしました。

OpenSTFが構築されているサーバーと端末は、電源供給型のUSBハブで接続されています。端末の充電時間の調整は、このUSBハブの電源部分に時刻/曜日で電源供給のON/OFFができるタイマーを挟むことで実現しました。これにより、深夜や休日にトラブルが発生する可能性を低減しています。

モバイル端末の利用予約をしたい!

OpenSTFを導入したことで、モバイル端末の管理・利用効率が格段に向上しました。しかし、運用していくと、「端末の予約をしたい」という要望が出てきました。

従来の運用では kintone上の「モバイル端末管理アプリ」を用いて「この期間はこのモバイル端末を占有します」という『予約』が可能でした。プロジェクトの試験期間にあわせて試験用の端末を確保しておくイメージです。OpenSTFには、そういう期間指定の端末管理機能はありません。実際にOpenSTFへアクセスしたら別の人が目当ての端末を使っていた、という取り合いは避けたいものです。

必要なのは、

  • kintoneアプリから予約情報を取得して
  • (予約時間に)該当の端末が利用中の場合は、端末の利用を終了させ
  • 予約者が利用している状態にする

という動作です。

kintoneとOpenSTFを連携しよう

幸い、OpenSTFとkintoneには、共に様々なAPIが用意されています。
このAPIを使い、kintoneに登録された予約期間に、確実に予約した端末を利用できる状態を作ります。

1) kintoneアプリ上の予約情報を確認する

kintoneのAPI「レコード一括取得」を使用し、kintoneのレコード情報を取得することができます。このAPIを使用して、kintone上に作った「モバイル端末管理アプリ」から予約情報を取得します。

f:id:cybozuinsideout:20181218151844p:plain
予約情報の取得

2) 予約情報がある場合、該当の端末が利用されていないか確認する

OpenSTFのAPI「GET /devices/{serial}」を使用し、端末のSERIAL_IDを指定して情報を取得することができます。このAPIで情報を取得すると、該当の端末の利用状況がわかります。

f:id:cybozuinsideout:20181218151833p:plain
端末の利用状況確認

3) 利用されている場合は、強制的に利用を中断する

OpenSTFのAPI「DELETE /user/devices/{serial}」を使用し、強制切断を行います。
注意点として、このAPIに付与するAPI-TOKENは利用者のAPI-TOKENにしないと切断することができません。そこで、ログイン情報をもとにLDAPサーバーからメールアドレスを取得し、それをキーとしてOpenSTFのrethincDBからAPI-TOKENを取得します。

f:id:cybozuinsideout:20181218151823p:plain
未予約者を強制切断

4) 該当の端末を予約者が利用している状態に変更する

OpenSTFのAPI「POST /user/devices」を使用し、利用者を強制的に設定します。
こちらのAPIも、(3)と同様に予約者のAPI-TOKENを使用して実行する必要があります。

f:id:cybozuinsideout:20181218151805p:plain
予約者の利用状態にする

終わりに

OpenSTFを導入することで、管理しているモバイル端末の半数をラックに格納し、利用者・管理者双方にメリットを提供することができました。ソフトウェアだけではなく、ラックやセンサーなどを含めてうまく運用に乗せられたので、個人的にも満足度が高いです。 今のところOpenSTFはiOS端末を管理できないので、従来の貸し出し方式とハイブリッドな運用になってしまっていますが、そのあたりは今後のアップデートに期待しています。今後は端末の利用状況を分析し、効率の良い端末の破棄/追加も視野に入れていきたいですね。

RxSwift で多重実行防止と実行中の表現を簡潔に書く

$
0
0

こんにちは。モバイル開発チームに所属している小島です。

Rx を使うとコードをシンプルに書くことができるので好きです。今回はある処理(API呼び出しなどを想定)を同時に複数回実行しないような制限を実現する Extension を考えてみたので紹介したいと思います。

ActivityIndicator

RxSwift のサンプルコードに ActvityIndicatorというものがあります。 UIKit に入っている UIActivityIndicator とは全く関係ないので紛らわしいです😢

それはさておき、こいつは何をしてくれるかというと Observable を関連付けると内部のカウンタを +1 して、その Observable が dispose されると -1 してくれます。つまりある Observable が実行状態であれば ON で、終了(Disposeされている)状態であれば OFF というのを表現できます。これを使って実現したいと思います。

Observable を拡張して、flatMap(withLock:) を追加

こんな便利なクラスがあれば、あとはそこまで難しくありません。

extensionObservableType {
    funcflatMap<O>(withLock:ActivityIndicator, _ selector:@escaping (Self.E) throws ->O) ->RxSwift.Observable<O.E>where O :ObservableConvertibleType {
        returnself
            .withLatestFrom(withLock) { input, loading inreturn (input, loading)
            }
            .filter { (input, loading) inreturn!loading
            }
            .flatMap({ (input, loading) ->RxSwift.Observable<O.E>inreturn (try! selector(input)).trackActivity(withLock)
            })
    }

Observable に対して、flatMap(withLock:) という関数を拡張します。これは何をやっているかというと、先の ActivityIndicator を引数にとって、ActivityIndicator が実行状態であれば処理をスキップし、終了状態であれば引数に渡したクロージャーを実行します。クロージャーは Observable を返すことになっていて、ActivityIndicator と関連付けをします。

こうすることで、返ってきた Observable が実行状態であれば flatMap(withLock:) を呼び出した場合には処理がスキップされます。

サンプルコード

文章で書いても伝わりづらいと思うので、サンプルコードも作ってみました。

このサンプルコードでは、doLongtimeTask という関数が定義されていて、処理が終わったら Complete する Observable を返します。(あと、確認のためにコールされたときにラベルにカウントを表示しています)

privatefuncdoLongtimeTask() ->Observable<String> {
        self.callCount +=1self.callCountLabel.text ="`doLongtimeTask` called \(self.callCount) times."letasyncSubject= AsyncSubject<String>()

        lettime= DispatchTime.now() +5.0
        DispatchQueue.main.asyncAfter(deadline:time) {
            letresult="Complete! Endtime is \(Date().description)"
            asyncSubject.onNext(result)
            asyncSubject.onCompleted()
        }

        return asyncSubject.asObservable()
    }

また、ボタンタップ時の処理は以下のようにしています。flatMap(withLock:) に ActivityIndicator を渡すことで、doLongtimeTask がすでに実行中の場合は処理を実行しないようにできます。

self.lockObject = ActivityIndicator()

        self.executeButton.rx.tap
            .flatMap(withLock:self.lockObject, { [weak self] _ inreturnself?.doLongtimeTask() ?? Observable.error(MyError.assertion)
            })
            .subscribe(onNext: { [weak self] result inself?.alert(message:result)
            })
            .disposed(by:self.disposeBag)

更に嬉しいのは、ActivityIndicator は現在実行中のものがあるかどうかを asObservable で取り出せますので、以下のように実行中に UIActivityIndicator (スピナーのほう!) のアニメーションを ON にするといったことも簡単にできます。

self.lockState =self.lockObject.asObservable()

        self.lockState
            .bind(to:self.progressIndicator.rx.isAnimating)
            .disposed(by:self.disposeBag)

こんな感じでできます

EXECUTE ボタンを何度押しても、ちゃんと doLongtimeTask が1回しか実行されないこと表しているアニメーション

EXECUTE ボタンを何度押しても、ちゃんと doLongtimeTask が1回しか実行されないことがわかると思います。また、実行時に UIActivityIndicator のアニメーションが動き、終わったら止まるといったことも実現できています。

Rx はパズルのように組み合わせて、いろんな機能を実現できるので楽しいですね。それでは。


kintone の性能改善について紹介します

$
0
0

こんにちは、ミドルウェア開発チームの青木(@a_o_k_i_n_g)です。

今回は、サイボウズ製品のひとつである kintoneに対して行った性能改善の成果を紹介したいと思います。kintone は面倒なコーディング無しに業務アプリケーションのようなものを作ることができ、様々なデータを格納したり、複雑な条件で検索したりソートしたり、アクセス権もきめ細やかに設定したりできるというサービスです。この kintone はサービスの性質上、多種多様で複雑なクエリを発行します。またデータ量も膨大で MySQL だけで計数十テラバイトのデータが存在しており、クエリの処理時間が長時間かかってしまうこともあります。

サイボウズでは kintone の性能改善に力を入れており、今回はその成果を紹介しようと思います。

はじめに

kintone はテナントごとに大きく使い方が異なり、性能改善が効くケースもあれば効かないケースもあります。例えば kintone 上のデータが 1000 件なのか 100 万件なのかで効果の有無は変わりますし、kintone 上のデータの内容や検索する条件、アクセス権の設定などでも効果は大きく変わります。

そのため今回紹介する改善例は必ずしも全ユーザーに効果があったわけではないことをご了承ください。また、グラフの縦軸目盛りは非表示にしていることも合わせてご了承ください。

kintone の性能改善で得た MySQL に関する技術的な知見はこちらの記事で紹介しているので、ご参考になれば幸いです。 blog.cybozu.io

改善リリース1: 最適化機構の改善

kintone には以前からクエリの最適化機構が備わっており、この機構を通すことで MySQL のレコードスキャン数が最小で済むようなクエリを出力できます。この最適化機構では与えられた検索条件を元にクエリの最適な JOIN 順序を決定したり、あるいはもっとダイナミックにクエリを書き換えて最速のクエリを出力するような処理を行ったりしています。このリリースではこの最適化機構を改善し、より多くのパターンのクエリについて最適化機構を通すようにし、レコードスキャン数の削減を行いました。詳しくはJOIN する順序を制御しようを参照ください。

この修正は、最適化が効くケースでは大きな効果がありました。これは最適化がよく効いたテナントのレスポンスタイムのグラフです。横軸が時間、縦軸がレスポンスタイム95パーセンタイル値を表します。今回の改善に関連する API ごとに色分けしており、いずれもレスポンスタイムが減少しました。 f:id:cybozuinsideout:20190129194638p:plain

この改善で Innodb_rows_readがおよそ半減しました。これは特定のテナントの値ではなく kintone 全体で見た値で、莫大な量のスキャン数を削減出来たことを表しています。 f:id:cybozuinsideout:20190129194708p:plain

改善リリース2: アクセス権判定処理の改善

kintone では、kintone 上で扱うデータにアクセス権を付与することが出来ます。このアクセス権はユーザー側としては非常に便利な機能なのですが、クエリを重くするひとつの要因でもあります。アクセス権判定処理はクエリ上ではサブクエリとして表現され、場合によってはそのサブクエリ大量のレコードをスキャンします。この部分を改善し、カバリングインデクスを使うよう修正しました。カバリングインデクスについてはカバリングインデクスを活用しように記載されているのでご参照ください。これはデータを選択する際にインデクスデータのみのスキャンで済むようにしたもので、レコードスキャン数が多かったり何度も実行されたりするようなケースでは特に効果があります。

その結果、データ取得系 API のレスポンスタイムが改善しました。劇的な改善というほどではありませんが、この改善はほとんどすべてのユーザーに対して効くものです。 f:id:cybozuinsideout:20190129194730p:plain

改善リリース3: 巨大オフセットの対応

kintone のデータをバックアップするなどの目的で、kintone のデータをクロールするようなアクセスはしばしばあります。kintone 上に数十万件やそれ以上のデータを格納しているテナントの場合、社内では巨大オフセット問題と呼ばれている問題が発生します。

巨大オフセット問題とは、通常は遅くないクエリであっても「全データ100万件のうち99万件目から500件取得」というようなクエリだと遅くなる、という問題です。以前の記事中の巨大オフセットについてではユーザー側に啓蒙するだけに留まっていましたが、さらなる検討により一部のケースではクエリ側で改善できることが判明しました。一般的な kintone の重いクエリは複数のテーブルを JOIN しているのですが、特定のパターンに関して言えば JOIN 処理を行う前に OFFSET を処理できることが判明し、その改善を取り込んだものです。

こちらは非常にヘビーに kintone を使うことで社内で有名だったテナントのスロークエリ量です。塔のようなスロークエリの塊がほぼ無くなりました。 f:id:cybozuinsideout:20190129194753p:plain

この改善にマッチしたとあるテナントでは、スロークエリ量が 1/100 まで削減できたケースもありました。

一部のケースでは改善できましたが、巨大オフセットは未だ我々を悩ませる問題であり、ユーザー側で改善できるものでもあります。こちらにユーザー側で行える対策を記載しているので、今後も周知していきたいと考えています。

kintoneの大量レコード取得を高速化 - cybozu developer network

改善リリース4: 不要なクエリの削除

こちらの改善ではある不要なクエリを発行しないようにしました。以前は効果的であったキャッシュとしての意味を持つクエリだったのですが、データベースのバージョンやフレームワークが変わるにつれてキャッシュの意味を成さなくなり、それだけでなく性能劣化を引き起こすケースもあることが判明したので削除しました。

この改善は多くのテナントに効くものですが、中でも社内でヘビー級で有名な特定のテナントには非常に良く効き、関連する API のレスポンスタイム90パーセンタイル値がほぼ 1/10 にまで下がりました。 f:id:cybozuinsideout:20190129194810p:plain

改善リリース5: 最適化機構の改善

前述した最適化機構を更に改良し、より最適なクエリを発行するよう修正しました。MySQL がスキャンするレコード数が最小で済むようより多くの情報を最適化機構に取り入れた改善です。最適化が効くケースのテナントでは大きく改善し、日々のスロークエリ量を削減することに成功しました。 f:id:cybozuinsideout:20190129194825p:plain

改善リリース6: マシンリソース増強

とある環境に非常に重いリクエストを多数投げられており、データベースサーバーの CPU 使用率を限界まで使い切っていました。kintone のデータベースサーバーは一般的に見てハイスペックなマシンであり、リソースが足りなくなることはそう多くないのですが、このケースではデータベースサーバーの CPU を使い切ってしまい待ち行列が発生しているという状況でした。

発行されている重いクエリを改善するというのがおそらく一番真っ当な方法なのかも知れませんが、当時はその重い原因となるリクエストについて調査がさほど進んでいなかったことから CPU コア数を増強することにしました(その後クエリの改善も行っています)。CPU を増強したことにより待ち行列が解消され、レスポンスタイムが改善しました。 f:id:cybozuinsideout:20190129194838p:plain

Innodb_s_lock_os_waitsなどの各種項目でも改善が見て取れました。 f:id:cybozuinsideout:20190129194853p:plain

改善リリース7: カーネルパラメータの修正

このリリースでは 3 つの効果的な改善を取り込みました。ひとつ目はあるタイミングで意図せず CPU が省電力モードになってしまっていたのを修正したことです。CPU の省電力モード等のモードは Linux だと /sys/devices/system/cpu/cpu*/cpufreq/scaling_available_governorsで設定されており、powersaveから performanceに変更することで搭載している CPU を最高性能で動かせるようになります。

また、MySQL への効果的なインデクスを追加してあるサブクエリをカバリングインデクスにしたことと、アクセス権判定時のレコードスキャン数を減らすという改善を取り込み、いずれも効果がありました。

これらの改善により、kintone の 主要な API のレスポンスタイムがほぼ半減しました。特定のユーザーにのみ効果があるような改善ではなく、すべてのユーザーに効果があります。 f:id:cybozuinsideout:20190129194909p:plain

今後の kintone の性能について

おかげさまで kintone の契約社数は順調に伸びています。その分様々な kintone の使い方をするユーザーも増え、重いリクエストを連続で投げ続けられるなどは日常茶飯事です。

例えばこちらはとある環境下の MySQL のスロークエリ量です(この環境下には多数の kintone テナントがいます)。このグラフは半年間ほどのレンジで、日ごとにどれだけのスロークエリが発行されたかを表しており、右に行くにつれて増大し負荷がどんどん高まっていることがわかると思います。この環境では昨年11月の改善が効いたので最後は減っていますが、減らせていない環境も残念ながらあります。 f:id:cybozuinsideout:20190129194923p:plain

今回紹介したもの以外にも多数の性能改善を行っています。データベース側だけでなくアプリケーション側も改善し、例えば不要なリクエストを減らしたり不要な処理を削減したりなども行っています。また、特定のテナントが激しく kintone を利用しても他のテナントに性能的な影響が出ないようにする仕組みなども取り入れています。

昨年は kintone 史上最も性能改善が取り込まれた一年になりました。それでもまだ性能問題が無くなったわけではなく、ご迷惑をお掛けしているお客様もいます。今後も引き続き快適な kintone を使えるよう日夜努力して性能改善を行っていく予定です。今後の kintone の性能にご期待ください。

組織変更したら部長がいなくなりました

$
0
0

こんにちは、最近愛媛から広島に移住した組織運営チームの水戸です。

2019年からサイボウズの開発本部から職能・地域毎に分かれた部署がなくなり、チーム主体の組織になりました。 組織変更をオープンに議論するというチャレンジングな試みの中で、新組織の理想はユーザー価値の最大化に定まり、個人やチームがより主体的に動ける組織構造に変わりました。 この記事では私がファシリテートを担当した組織変更をご紹介します。

開発本部の状況

開発本部の役割は製品を開発することです。 2018年までの開発本部はマトリクス組織を採用しており、プロダクト開発チームには様々な職能・地域毎に分かれた部署のメンバーが所属していました。 この組織構造は事業の中心がオンプレミスだった10年以上前から、事業の中心がクラウドに移った2018年に至るまで変わっていません。 f:id:cybozuinsideout:20190212135643p:plain

一方、プロダクト開発チームに求められるものは大きく変わりました。 短いサイクルで開発した製品を安全にリリースし、運用本部と連携して安定運用し、得られたデータを開発に活かしながらより多くのお客様に価値を届け続けていく事の重要性は高まっています。 このようなオンプレミスからクラウドへの変化にあわせて、開発スタイルはウォーターフォールでの指揮統制・分業型からスクラムでの自己組織化・機能横断型に大きく変わりました。

組織変更の議論

開発スタイルが変化することで職能に閉じない活動が増え、マトリクス組織ではやりづらい部分が出てきました。 これまでのようにマトリクス組織のまま改善していくという選択肢もありましたが、「組織構造がチームの改善の大きな妨げになっているのでは?」、「今の状況に対してマトリクス組織が最適なのか?」という問題意識から組織変更の議論が始まりました。

議論はマネージャーと関係者のみでクローズドに進めるのではなく、オープンに進めました。 組織変更という人に関連する議論をどこまでオープンにできるのか懸念はありましたが、組織変更の効果を高めるには実現したい理想が広く知られている状態にすることが重要と判断しました。 また問題意識や改善のアイデアを広く収集できるという期待もありました。

問題意識の収集はヒアリング中心で、できるだけ多くの人から話を聞く方針で進めました。 その結果、開発本部の日本の全チームの複数職能を網羅するだけでなく、業務で関わりのある他本部も対象に約60組からお話を聞かせてもらいました。 ヒアリングした内容はヒアリング対象者の合意を得た上でほぼ全て公開していきました。 公開することで、自分にもヒアリングしてほしいというリクエストをもらえたり、組織変更を待たずチーム内での改善が始まったりするなどの良い効果もありました。

ヒアリング結果をごく一部ですが紹介します。

  • QAが回帰試験を自動化しているが、プログラマーも協力したほうが早く進みそう
  • 職能を越えて改善はしているが他職能に踏み込むハードルは高い
  • 職能と組織と権限の関係がよくわからない(例:元プログラマーのデザイナーは不具合改修してよいのか)
  • 活動の相談相手はチームメンバーだが承認者はチーム外のマネージャーで説明コストが高い
  • 異動に一大事感がある
  • マネージャーが異動やキャリアに権限持ちすぎ

また、同じプロダクトに関わっていてもミッションは職能毎に作られ、他職能にミッションがあまり知られていないケースもありました。 これらヒアリングを整理し、組織変更の最大のテーマをユーザー価値の最大化に定め、サブテーマを4つに絞りました。

  1. ミッションのズレを避け組織・職能を越えて協力していくには
  2. チームとしてもプロダクトとしても継続的に学習していくには
  3. 個人が専門性を高めたり、チーム間で知見を共有しやすくするには
  4. 個人・チーム・マネージャーがどう役割分担するのが良いか

上記テーマを基に誰でも自由に参加可能な形式で議論を重ね、新組織を形作っていきました。

新組織

新組織では職能・地域毎に分かれた部署がなくなり、開発本部の直下にチームがフラットに並びます。 チームには製品開発をミッションに持つkintoneやGaroonの他、他のチームを支援するAgile Coachや生産性向上があります。 部署がなくなることで部長という役割もなくなり、予算・勤怠など部長権限はチームに委譲されました。 あわせて責任範囲も見直し、「開発完了の起案者はQA」等の職能に紐付いた社内ルールもなくなりました。 また、個人が専門性を高めたりチーム間で知見を共有しやすくするために、自由に設立・参加できるコミュニティ制を導入しました。 2019年の組織体制

以下が新組織の狙いです。

  • 職能単位での部分最適化ではなく、チーム全体で最適化しやすく
  • 従来の職能の枠にとらわれず、個人の多様なスキル・個性を活かしてプロダクトやチームに貢献できるように
  • チームに必要なことはチームで意思決定できるように、権限と責任をチームに委譲
  • 主体的にキャリアを作れるように異動をカジュアルに

今回の新組織は個人やチームが動きやすくなることを重視しています。 それを支援するチームとして組織運営チームが新設されました。 組織運営チームは開発本部のメンバーがやりたいことをやりやすくするためのチームで、元マネージャーが多く所属しています。

組織変更後

組織変更をして1ヶ月が経過しましたが大きな混乱は起きてないように見えます。 その理由としては組織変更により仕事のやり方を強制的に変更させられるわけではなく、今までと同じやり方を続けることも可能だったこともあると思います。 とはいえ何も変わらなければ組織変更は失敗なので、これからいろんなチームの取り組みが加速していくのが楽しみです。

また今回の組織変更は開発本部に絞りましたが、他本部ともっと一緒にやっていきたいという声も聞きました。 実際、運用本部やビジネスサイドとの動きが出はじめており、こちらも今後が楽しみです。

終わりに

本文では触れませんでしたが、これまで様々なチームが改善の取り組みをおこなってきました。

また、ヒアリングの中でも「こうすればもっと良くなりそう」というたくさんの思いを聞かせてもらいました。 新組織で障害が取り除かれ様々な改善がしやすくなると期待してますが、今回の組織変更は1ステップにすぎないので今後もユーザー価値の最大化のために最高の組織を目指していきます。

開発・運用本部向け新人研修2018の講義資料を公開しました

$
0
0

こんにちは。そろそろプロ野球シーズンの開幕が待ちきれなくなってきたコネクト支援チーム*1の酒井(@sakay_y)です。

サイボウズでは、新入社員全体研修の後に、開発系の新入社員に対して1ヶ月程度の開発研修をおこなっています*2。内容は、毎年改善を重ねていますが、基本的には講義+実習です。本記事では、先日公開した2018年の研修の講義資料を、全体の流れに沿って紹介したいと思います。

開発・運用研修について

本研修は「開発本部・運用本部に配属される新入社員が、部署配属後に必要となる基礎的な知識/技術/ツールを学び、体験できる。」ことを目的にしています。
新入社員3〜4名を1チームとして、そこに担当のメンターが1名付いて研修を進めていきました。講義では先輩社員に講師をお願いし、開発演習では各チームにメンターとは別の先輩社員が担当スクラムマスター(!)として付きました。

スケジュール

  • 7/2 〜 7/18
    • 環境構築
    • 各種講義、演習
    • HTTPサーバー開発
  • 7/19 〜 7/30
    • Webアプリケーション開発
  • 7/31
    • 成果発表会
    • 配属発表

講義について

開催された講義を時間割順に並べます。
公開するにあたって資料を編集したもの、公開できなかったものもあります。

1.開発運用研修について @okady

speakerdeck.com

2.ソフトウェアエンジニアの育ち方 @ymmt

speakerdeck.com

3.仮想マシンと開発環境構築

speakerdeck.com

4.Ubuntu Linux基礎 @uchan_nos

speakerdeck.com

5.Git/GitHub講義 + 演習

speakerdeck.com

speakerdeck.com

6.Webアプリケーション基礎

speakerdeck.com

7.ソフトウェアライセンス

speakerdeck.com

8.品質保証活動に関するテスト全般からテスト自動化まで

資料非公開

9.ネットワーク

資料非公開

10.HTTP/DNS @tignyax

speakerdeck.com

11.正規表現

speakerdeck.com

12.スクラムトレーニング

資料非公開

13.HTTPサーバー開発課題について @okady

speakerdeck.com

14.データベース @mitomasan

speakerdeck.com

15.セキュリティ

speakerdeck.com

16.Webアクセシビリティ @sukoyakarizumu

speakerdeck.com

17.誤解されにくい日本語の書き方

資料非公開

18.DevOps @miyajan

speakerdeck.com

19.Webアプリケーション開発課題について @okady

speakerdeck.com

開発・運用研修を終えて

以上が、今回開催された講義資料です。
講義の他に、HTTPのRFC(72307231)を読み込んだり、リーダブルコードを読んだり、体系的に学ぶ 安全なWebアプリケーションの作り方(通称:徳丸本)を読む時間も設けられました。
開発演習は、スクラム形式+モブプログラミングで進めました。講義の知識を活かしつつ、チームで開発することを体験して、新入社員の皆さんはチームに配属されるための土台ができたのではないでしょうか。研修の最終発表でも、チーム開発の難しさや得られた知見について、新入社員それぞれが語っていました。

ちなみに、新入社員からもフィードバックを受け付けていて、来年の研修の改善に活かされます。

kintone上での新入社員からのフィードバック一覧
新入社員から講義や演習のフィードバックをもらいます
このように、研修にはkintoneが使われており、講義資料と講義スケジュールをまとめたものや、日々のふりかえりの登録、連絡スペースなど、用途は様々です。

最後に

今回、このように昨年の研修資料を公開しましたので、各社のエンジニア研修や駆け出しエンジニアの皆様のお役に立てれば幸いです。
また、学生の皆さんには「弊社の研修はこんな感じですよ」というのが伝わって興味を持ってくれたら嬉しいです。2020年新卒採用およびキャリア採用やっています!

サイボウズ | 採用情報(新卒・キャリア)

*1:サイボウズを好きになってもらうため、社内と社外のエンジニアをつなげる仕事をしてます!

*2:中途入社の方は、最初からチーム に配属されての研修になります

ストレージデバイスの暗号化に用いる暗号スイート間の性能比較

$
0
0

はじめに

Necoプロジェクトのsatです。

cybozu.comの次世代インフラ基盤Necoにおいてはストレージデバイスが紛失や盗難にあったときでもお客様のデータの漏洩を防ぐために、ストレージデバイスの暗号化をしています*1。ストレージデバイスの暗号化にはLinuxのdm-cryptを使っています。本記事では、この機能を使うにあたって、どの暗号スイートを使うかを処理速度という観点で決めた際の評価結果、それをもとにした判断基準、および評価の流れについて説明します。

dm-cryptに使える暗号スイートはたくさんありますが、まずは以下の要件を満たすものを選びました。

  • 世間でよく使われている
  • Necoで使用するにあたって十分な強度を持つ
  • 暗号化/復号においてCPUに専用命令がある

この観点に絞ったところ、下記4つが候補に残りました。

  • AES-128-CBC-ESSIV:SHA256
  • AES-256-CBC-ESSIV:SHA256
  • AES-128-XTS-PLAIN64
  • AES-256-XTS-PLAIN64

ここから上記4つの性能比較によって、Necoにおいてどの暗号スイートを使うかを決めました。

ここでXTSについて一点補足しておきます。XTSには一度に暗号化するデータ長が長くなると問題になるケースがあります。

https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/aes/XTSVS.pdf

The length of the data unit for an instance of an implementation of XTSAES SHALL NOT exceed 220 blocks

しかしdm-cryptにおいては一度に暗号化するデータ長が220を超えることはないため、この問題はありません。

判断基準

前節において述べた4つの暗号スイートからどれを選ぶかについて、おおまかな判断基準を次のように定めました。

  • CBC-ESSIV:SHA256とXTS-PLAIN64という2つのモードのうち高速なものを選ぶ
  • 2つの鍵長128bitと256bitについては前者のほうが明らかに高速であれば前者を、それ以外はより強度が高い後者を選ぶ

測定環境

  • サーバ: Dell EMC Power Edge R640
  • CPU: Xeon Gold 5120 (2.2GHz, 14コア, 19MB キャッシュ, TDP 105W) * 2
  • メモリ: 512 GB
  • ストレージデバイス: Intel SSD DC P4500 4TB
  • OS: Core OS Container Linux 1967.6.0
  • カーネル: 4.14.96-coreos-r1
  • ファイルシステム: ext4
  • ベンチマークプログラム: fio 3.7

なぜこの環境にしたのかについては後述します。

測定条件

以下の総当たりです。

  • 暗号化方式: AES-128-CBC-ESSIV:SHA256, AES-256-CBC-ESSIV:SHA256, AES-128-XTS-PLAIN64, AES-256-XTS-PLAIN64
  • I/Oサイズ: 4k, 128k
  • I/Oパターン: seqread, seqwrite, randread, randwrite
  • 並列数: 1, 2, 4, 8, 16, 32, 64, 128

なぜこれらの組み合わせにしたのかについては後述します。

測定プログラム

# echo 0 >/sys/block/nvme1n1/read_ahead_kb # ブロックデバイス層によるデータの先読みを防ぐ
# cryptsetup --cipher=<暗号スイート> \
  --key-size=<キー長(XTS-PLAIN64の場合はキー長が128なら256,256ならば512を指定する必要がある。詳細については割愛)> \
  --key-file=key open --type plain <device(/dev/nvme1n1 or /dev/ram0> enc
# mkfs.ext4 crypt /dev/mapper/enc
# mount /dev/mapper/enc /data
#  fio -direct=1 -readwrite=<I/Oパターン> -group_reporting \
        -filename=/data/testfile -size=20G -runtime=60 \
        -bs=<I/Oサイズ> -numjobs=<並列度> -name=file1
# echo 3 > /proc/sys/vm/drop_caches

結論

AES-256-XTS-PLAIN64を採用することに決めました。理由はXTS-PLAIN64のほうがCBC-ESSIV:SHA256よりも顕著に高速であり、かつ、鍵長の差による性能差が顕著ではなかったためです。

細かい評価基準については後述します。

評価の流れ

ここからは前述の結論に至るまでの評価の流れを記載します。

何を測ればいいのか

性能測定は目的を定めずに既存のベンチマークテストを適当に流すだけでは意味がありません。最も望ましいのは実際のワークロードを流してみて、そのときの性能を見ることです。しかし、現在のところNecoは開発中なので、実際のワークロードはわかりません。今回は次のような基礎的な性能データをとることにしました。

  • sequential read
  • sequential write
  • random read
  • random write

性能と言っても色々ありますが、ここではIOPSとbandwidthを測定することにしました。

測定環境は、すでに手元にあるNecoにおいて使う予定のサーバ、およびストレージデバイスとしました。

ストレージデバイスはNVMe SSDとHDDの二種類があります。後者については暗号化/復号処理の速度に対してストレージデバイスそのものの処理速度が遅すぎるため、データをとってもさほど意味はない、もし仮に採取するとしても今やらなくてもいい、という判断でデータ採取をしませんでした。ストレージデバイスそのものの処理速度に依存しない、生の暗号化/復号処理にかかる時間も採取したかったので、メモリに作成するブロックデバイスであるbrd(デバイス名は/dev/ram*)上でも性能測定をすることにしました。

NVMe SSDないしbrdという2つのブロックデバイス上のデータはdm-cryptを用いて暗号化しました。そのときに使った暗号スイートは前述の4つです。

性能測定対象のファイルシステムはNecoで使用予定のext4としました。

I/Oサイズについては以下の2パターンとしました。

  • 4KB: ストレージデバイスをローカルストレージとして使う場合を想定。ファイルシステムから見るとこれ未満のサイズのI/Oはあまり出ないのでこの値を採用した
  • 128KB: ストレージデバイスをCephのブロックデバイスの一部として使う場合を想定。Cephのブロックデバイスでは通常4MB単位でI/Oが出るが、今回使用するデバイスにおける一回のI/Oあたりの最大データ量が128KB*2なのでこの値を採用した

I/Oは複数のスレッドから同時に発生することがある、むしろそのほうが普通なので、並列にI/Oが出るパターンも測定しました。マシンのコア数が28だったので、その4倍程度の最大128並列I/Oまでのデータをとることにしました。

ベンチマークプログラムには上記のような測定をするのに便利なfioを使いました。

測定結果と考察

本節では性能データの測定結果をグラフ化するとともに、暗号スイートによってどのような違いが出るのかを考察しました。グラフが大量にあるので、細かいデータに興味の無い人は考察だけ見てください。

brd

まずはストレージI/Oを除いた暗号スイートの速度差がそのまま性能差にあらわれるbrdについてのデータです。

測定結果

  • 4k-seqread

f:id:cybozuinsideout:20190307192929p:plain

f:id:cybozuinsideout:20190307192947p:plain

  • 4k-seqwrite

f:id:cybozuinsideout:20190307193009p:plain

f:id:cybozuinsideout:20190307193023p:plain

  • 4k-randread

f:id:cybozuinsideout:20190307193044p:plain

f:id:cybozuinsideout:20190307193055p:plain

  • 4k-randwrite

f:id:cybozuinsideout:20190307193113p:plain

f:id:cybozuinsideout:20190307193125p:plain

  • 128k-seqread

f:id:cybozuinsideout:20190307193220p:plain

f:id:cybozuinsideout:20190307193234p:plain

  • 128k-seqwrite

f:id:cybozuinsideout:20190307193247p:plain

f:id:cybozuinsideout:20190307193303p:plain

  • 128k-randread

f:id:cybozuinsideout:20190307193319p:plain

f:id:cybozuinsideout:20190307193331p:plain

  • 128k-randwrite

f:id:cybozuinsideout:20190307193349p:plain

f:id:cybozuinsideout:20190307193400p:plain

考察
  • 暗号スイート同士の差はある。読み出し(復号)よりも書き込み(暗号化)のほうが顕著
  • 書き込みについては暗号スイート間の速度は降順に並べると次のようになる
AES-128-XTS-PLAIN64 > AES-256-XTS-PLAIN64 > AES-128-CBC-ESSIV:SHA256 > AES-256-CBC-ESSIV:SHA256
  • 読み出しについてはおおよそ次のようになる。
AES-128-CBC-ESSIV:SHA256, AES-256-CBC-ESSIV:SHA256 > AES-128-XTS-PLAIN64 > AES-256-XTS-PLAIN64

この理由には次のようなことが考えられます。

  • CBCモードは暗号化が並列化できないものの、復号は並列化可能
  • XTSモードは暗号化/復号共に並列化可能
  • XTSモードが復号においてCBCモードより遅いのは、XTSモードは2個のAESと特別な演算を行うから
  • XTSモードは暗号化においては並列化・専用命令により、現代のCPUにおいてはCBCモードよりも有利だから

NVMe SSD

測定結果

  • 4k-seqread

f:id:cybozuinsideout:20190307193458p:plain

f:id:cybozuinsideout:20190307193508p:plain

  • 4k-seqwrite

f:id:cybozuinsideout:20190307193519p:plain

f:id:cybozuinsideout:20190307193531p:plain

  • 4k-randread

f:id:cybozuinsideout:20190307193549p:plain

f:id:cybozuinsideout:20190307193602p:plain

  • 4k-randwrite

f:id:cybozuinsideout:20190307193631p:plain

f:id:cybozuinsideout:20190307193642p:plain

  • 128k-seqread

f:id:cybozuinsideout:20190307193707p:plain

f:id:cybozuinsideout:20190307193721p:plain

  • 128k-seqwrite

f:id:cybozuinsideout:20190307193734p:plain

f:id:cybozuinsideout:20190307193748p:plain

  • 128k-randread

f:id:cybozuinsideout:20190307193807p:plain

f:id:cybozuinsideout:20190307193831p:plain

  • 128k-randwrite

f:id:cybozuinsideout:20190307193840p:plain

f:id:cybozuinsideout:20190307193851p:plain

考察

  • 暗号スイートの速度を降順に並べるとおおよそ次の通りになる
AES-128-XTS-PLAIN64 > AES-256-XTS-PLAIN64 > AES-128-CBC-ESSIV:SHA256, AES-256-CBC-ESSIV:SHA256 
  • 読み出しについては暗号スイートによる差はせいぜい数%程度
  • 書き込みは読み出しよりも暗号スイートによる差が大きい傾向にある。4kのほうが128kよりも差が大きい。前者は数%程度だが、後者は数十%ほど。

その他気になったところは次の通りです。

  • 128k-seqread, 128k-randreadが高並列度においてIOPSが横ばいになっている理由はSSDのIOPSの限界と考えられる
  • 4k-seqwrite, 4k-randwriteが低並列度からIOPSが横ばいになっている理由はSSDのIOPSの限界と考えられる
  • 128k-seqwrite, 128k-randwriteにおいてIOPSが横ばいになっている理由はSSDのbandwidthの限界と考えられる

最終判断

前節における考察をもとに、次の理由からAES-256-XTS-PLAIN64を採用することにしました。

  • XTSモードのほうが性能的には有利。並列読み出しにおいてはCBCモードのほうが高速であるが、それ以外はおおよそXTSモードのほうが高速。前者についてはNVMe SSDにおいて全体の性能への寄与が小さい
  • XTSモードの鍵長については全体的に128bitのほうが高性能だが、ほとんどの場合は数%以内の差におさまる。特定のケースでは大きな差が出ることもあるが、その場合も20%程度におさまる

おわりに

いかがでしたでしょうか。本記事では4つの暗号スイートを使ったストレージデバイスの暗号化の性能差、および、暗号スイートに依存しない一般的な性能測定の流れの一例を書き綴ってみました。これらが読者のみなさまの参考になれば幸いです。

Necoプロジェクトでは一緒に働いてくれるかたを絶賛募集中です。興味があればご応募願います。

cybozu.co.jp

www.wantedly.com

*1:もちろん現在のインフラ基盤においても同様です

*2:/sys/block/{デバイス名}/queue/max_hw_sectors_kbによって得られる

ドキュメントサイトの管理にはNetlify+静的サイトジェネレーターが便利

$
0
0

こんにちは!開発部テクニカルコミュニケーショングループの仲田(@naoh_nak)です。
最近WeWorkみなとみらいに出没し始めました。おしゃれ過ぎて少し落ち着かないのですが、慣れたら自分もそちら側の人間だと思うようになるのかもしれません。

前回はヘルプサイトをマークダウンで制作する話をしました。そのサイトのホスティングにNetlifyを使うことでいい感じに制作プロセスを回せているので、今回はその話をします。

Netlifyもう使ってるよ!という方には今更の内容かもしれませんが、ブログなど小規模なサイトの運用に使っているケースが多いのではと思います(ネットにある情報を見る限り)。サイボウズのヘルプサイトは1万ページを超え、日英中3言語で運用しています。このような大規模なサイトでの運用例としての参考にもなれば嬉しいです。

Netlifyとは

Netlifyって何?って方もいますよね。Netlifyは、静的サイトのホスティングサービスです。

ホスティングといっても、AWS(Amazon Web Services)やGCP(Google Cloud Platform)などとは違いフルマネージドサービスなので、サーバーの管理は不要です。公開したいコンテンツを用意するだけで、サイトを公開できます。

ただし、静的サイト専用なので、WordPressで作るサイトのような動的サイトをNetlifyで運用することはできません。動的サイトを運用する場合は、AWSやGCPなどでサーバーを用意する必要があります。

サーバーサイドでの処理を行わない静的サイトには、表示速度や脆弱性対策の面で強みがあります。表示速度はUXやSEO対策のために重要ですし、特に企業のサイトでは脆弱性対策も重要です。ヘルプサイトを含めたドキュメントサイト、ブログ、プロモーションサイトなど、たいていのサイトは静的サイトとして運用可能でしょう。

Netlifyのいいところ

Netlifyのいいところは、とにかく簡単にサイトを作れることです。最短では、HTML、CSSやJSをNetlifyのUIにドラッグ&ドロップするだけでサイトを公開できます。

f:id:cybozuinsideout:20190307180820g:plain
ドラッグ&ドロップでサイトを作成

URLも自動で割り当てられます。デフォルトでは「○○○.netlify.com」のようなドメインになりますが、もちろん独自ドメインを割り当てることもできます。

バージョン管理システムとの連携

とはいえ、コンテンツの数が多くなると、ドラッグ&ドロップでサイトを更新していくのは辛くなってきます。きちんとシステムでコンテンツを管理する必要が出てきます。

前回の話のとおり、GitHubやGitLabなどのバージョン管理システムを使うと、コンテンツの管理が容易になります。チームでのサイト更新の共同作業がやり易くなりますし、いつ、誰が、どのような目的で、どのようにコンテンツを変更したのかを記録できるようになります。コンテンツの更新内容を制作担当者間で効率よくクロスチェックしたり、必要に応じてコンテンツを過去の状態に戻したり、といったことも可能になります。

バージョン管理システムとの連携が強いのも、Netlifyのいいところです。バージョン管理システムのアカウントでNetlifyにログインして、コンテンツを置いたリポジトリを選ぶだけで、選んだリポジトリからNetlifyがコンテンツを取得してサイトを公開してくれます。

f:id:cybozuinsideout:20190307181132p:plain
リポジトリを選んでサイトを作成

リポジトリ上のコンテンツを更新すると、自動で公開サイトに反映されます。自前でサーバーを組むと、このようなサイト更新処理の自動化にはCIツールを使った組み込みが必要になるところですが、Netlifyを使うとそのような組み込みも不要になります。

ブランチごとのテストサイトの作成

リポジトリに複数のブランチがある場合は、公開用のブランチだけでなく、それ以外のブランチのテストサイトもNetlifyが自動で作ってくれます。ブランチを切ってサイトを更新する場合、切り出したブランチのテストサイトが自動作成されるので、サイトの表示確認が簡単にできます。ブランチをマージするときには、マージした結果をプレビューできるテストサイトまで作成してくれます。このテストサイト作成機能はめっちゃ便利です。作成されたテストサイトを制作チーム内でシェアして、公開前にチェックして貰うと良いでしょう。

f:id:cybozuinsideout:20190307191130p:plain
ブランチごとにテストサイトを作ってくれる

静的サイトジェネレーターの組み込み

Netlifyには、Jekyll、Hugo、Mkdocsなど多数の静的サイトジェネレーターが組み込まれています。なので、マークダウンなどで書いたコンテンツをGitHubやGitLabで管理しておけば、Netlifyのサーバー上でHTMLに変換して公開してくれます。

コンテンツを更新するたびにHTMLに変換し直すのは地味に手間がかかります。これを自前で行わなくて良いのは楽です。

マークダウンの状態でコンテンツをバージョン管理できるのには、差分管理の面でもメリットがあります。マークダウンは可読性が高いので、コンテンツをどう変更したのかわかり易くなります。HTMLの状態でコンテンツをバージョン管理すると、差分にHTMLタグが混じってしまうため、純粋な差分を判別しづらくなります。

そのほかのいいところ

そのほかにも、Netlifyには次のような便利な機能が揃っています。

  • CDNによって世界各地からサイトに高速にアクセス可能
  • サイトが自動でHTTPS化され、SSL証明書の発行や更新もしてくれる
  • リダイレクトを設定可能(企業サイトには必要ですよね)
  • A/Bテストも可能

ここで紹介しなかった機能もありますので、詳しくはNetlifyのドキュメントをお読みください。

Netlify+静的サイトジェネレーターを使った大規模サイト運用

さて、このように便利なNetlify+静的サイトジェネレーターの組み合わせですが、大規模なサイトで使おうとすると、コンテンツを単体のリポジトリでは管理しきれないことが障壁になってくると思います。その場合の回避方法も紹介します。

大規模なサイトのコンテンツをGitHubなどのバージョン管理システムで管理しようとすると、コンテンツを複数のリポジトリに分けて管理したいケースが出てきます。たとえば、サイボウズのヘルプサイトでは、プロダクトごとにリポジトリを分けてコンテンツを管理しています。万単位のファイルがあるので1リポジトリで管理するにはファイルが多過ぎますし、プロダクトごとに担当者が分かれているのでリポジトリも分けたほうが運用し易いからです。

Netlifyで作るサイトはリポジトリに紐付くので、複数のリポジトリから1つのサイトを作ることはできません。リポジトリの数だけサイトができます。では、1つの大規模サイトのコンテンツを複数のリポジトリで管理したい場合はどうすれば良いのでしょうか?

Netlifyのプロキシー機能

方法の1つが、Netlifyのプロキシー機能を使うことです。この機能を使うと、Webブラウザーのアドレスバーに表示されるURLを変えずに、別のサイトのコンテンツを表示することができます。

プロキシーの設定

以下は具体的な設定例です。ここでは、次の3つのディレクトリーを持つサイトを想定します。

  • https://example.com/a/
  • https://example.com/b/
  • https://example.com/c/

サイボウズのヘルプサイトであれば、プロダクトごとにディレクトリーを分けているので、ディレクトリーa、b、cのそれぞれがプロダクトと対応します。

ディレクトリーごとにリポジトリを分けてサイトのコンテンツを管理することにします。その場合、GitHubには、次の4つのリポジトリを作成します。ルートディレクトリーのコンテンツを管理するリポジトリも作るのがポイントです。

  • ディレクトリーa配下のファイルを管理するリポジトリ(以下、リポジトリA)
  • ディレクトリーb配下のファイルを管理するリポジトリ(以下、リポジトリB)
  • ディレクトリーc配下のファイルを管理するリポジトリ(以下、リポジトリC)
  • ルートディレクトリーのファイルを管理するリポジトリ(以下、リポジトリR)

そして、Netlifyでリポジトリごとにサイトを作ります。

  • リポジトリAに紐づくサイト(以下、サイトA)
  • リポジトリBに紐づくサイト(以下、サイトB)
  • リポジトリCに紐づくサイト(以下、サイトC)
  • リポジトリRに紐づくサイト(以下、サイトR)

f:id:cybozuinsideout:20190307185343p:plain
リポジトリとサイトを作成

上記のようにサイトを作った上で、サイトR(ルートディレクトリーのサイト)に、次のプロキシーを設定します。

/a/*  https://a.netlify.com/:splat  200
/b/*  https://b.netlify.com/:splat  200
/c/*  https://c.netlify.com/:splat  200

「https://a.netlify.com/」はサイトAのURL、その他のURLも、ぞれぞれサイトBとサイトCのURLを表します。

プロキシーの設定の書き方はNetlifyのドキュメントを参照いただきたいですが、上記の設定は次の意味を持ちます。

  • ディレクトリーA配下のページ(https://example.com/a/index.htmlなど)にアクセスしたらサイトAのページを開く
  • ディレクトリーB配下のページ(https://example.com/b/index.htmlなど)にアクセスしたらサイトBのページを開く
  • ディレクトリーC配下のページ(https://example.com/c/index.htmlなど)にアクセスしたらサイトCのページを開く

「*」はワイルドカード指定です。「:splat」と書かれた部分は、「*」の部分のパスに変わります。つまり、 https://example.com/a/foo/bar.html にアクセスしたら、 https://a.netlify.com/foo/bar.html のページが表示される、ということになります。

サイトに訪れるユーザーは、サイトRにアクセスします。なので、サイトに独自ドメインを使う場合は、サイトRに対してドメインを割り当てます。

f:id:cybozuinsideout:20190311155635p:plain
プロキシーの設定

Hugoを使う場合に必要な設定

サイボウズのヘルプサイトでは静的サイトジェネレーターとしてHugoを使っていますが、Hugoを使う場合はサイトA、B、CのHugoの設定ファイル(config.toml)に「baseURL」オプションを指定する必要があります。baseURLの値には、それぞれのサイトのディレクトリーパスを指定します。上記の例であれば、サイトAではbaseURLに「/a/」を、サイトBでは「/b/」を、サイトCでは「/c/」を指定します。

以上、1つのサイトのコンテンツを複数のリポジトリで管理する場合に必要な設定の紹介でした。

最後にNetlifyの良くないところも

最後に、日本から使う場合にNetlifyのイマイチな点も書いておきます。

サポートが英語

(自分みたいな)英語苦手な人間には少し辛いです。英語。。

あと、日本だと「いつもお世話になっております。」から返答が始まるところですが、アメリカの人はいきなり「Hi!」とか返してくるので、文化の違いに若干戸惑ったりします。あるあるですね。こっちもテンション上がって「Hello!」と返します。

サポート時間がアメリカタイムゾーン

英語はまだ何とかなりますが、記事執筆時点ではサポートの稼働時間がアメリカ西海岸のタイムゾーン(夏時間で日本時刻-16時間)なので、問い合わせへの返答は基本的に次の日になります。どうしてもやり取りに時間がかかります。 なお、Enterpriseプランで契約すれば、24時間サポートが受けられます。EnterpriseプランだとSLAもあるので、企業で使う場合はこのプランになるでしょう。

サポートの人が強そう

サポートはチャットでの連絡になり、相手の写真が表示されるんですが、向こうのエンジニアってやたら体鍛えますよね。そんな腕でキーボード打ったら壊れるやろって思うんですが。 戦ったら負けそうなので、問い合わせの口調が少し弱気になります。

おわりに

今回は、ドキュメントサイトの運用にNetlifyと静的サイトジェネレーターを使うメリットをお話しました。サイトが大規模になるとコンテンツ管理のリポジトリを分けるケースも出てきますので、その場合に必要な設定についても紹介しました。 静的サイトジェネレーターを使うならサーバーはNetlify一択ではないかと思うほど使いやすいので、ぜひ使ってみてください。

Viewing all 699 articles
Browse latest View live