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

【iOS】画像選択系UIを作る上で必要な知識

$
0
0

こんにちは、モバイルチームの松元(@daikimat)です。

iOSアプリを作る上で画像選択のUIを作るということはよくあることと思います。そんな時に使える汎用的なライブラリを作成しました。この記事では今回作成したライブラリの紹介と、画像選択UIを実装する上で必要なPhotoKit / Photos frameworkの基礎的な知識やハマりポイントを紹介します。

ライブラリを作った背景

昨年、モバイルでのあるユーザーシナリオを検証するためプロトタイプを作り一部のユーザー様とベータテストを行ないました。 その中で「画像をサクサク撮って添付したい、既にフォトライブラリに保存されている画像もサクサク添付したい」といったシナリオがあり、カメラ撮影とフォトライブラリの画像の選択を手軽に行えるiMessageのUIを参考に実装しました。 そのプロトタイプはリリースには至りませんでしたが、この画像選択UI部分は汎用的ということで昨年行われたハッカソンで汎用化 + ライブラリ化し社内限定で使えるようにしていました。 せっかくなので今回社外にも公開し、実装時にたまった知見も共有することにしました。ちなみに参考にしたiMessageの画像添付UIはiOS12からiMessage Appと一体化して今は無きUIとなりました。

AttachmentInput

画像選択UIライブラリは ↓で公開しています。
github.com

UIResponderinputViewに当ててキーボードとして使うことを想定しています。
iOS11のiMessageのUIにそっくりですが、iMessageと違う点でいうと画像や動画の圧縮やファイル名、ファイルサイズの取得にも対応しています。
他にもやりたいことはissuesに追加しています。至らない点が多々あると思います。英文がおかしいとかリファクタリングでも良いのでPRをお待ちしています。

AttachmentInput使用イメージ
AttachmentInput ダサい名前だが略せばAI、今流行り

Tips集

ここからがタイトルに書いた本題ですが、上記ライブラリを作るにあたってPhotos frameworkに関して情報少ないなー、辛かった気がするなーという点をこちらに列挙しておきますのでどなたかのお役に立てれば幸いです。

画像と動画のみの一覧を取得する

画像を取得するにはPhotoKitのPhotos frameworkを使って画像や動画などを表すPHAssetを取得することになります。 PHAssetの種類であるPHAssetMediaTypeによるとimagevideoの他にaudiounknownタイプがあって、ここでは不要なのでPHFetchOptionsで取得しないように指定します。他にもアセットの取得数の上限や、ソート方法など指定することができます。

letfetchOptions= PHFetchOptions()
fetchOptions.fetchLimit =100// 一度に取得するアセットの数
fetchOptions.predicate = NSPredicate(format:"mediaType == %d || mediaType == %d",
                                      PHAssetMediaType.image.rawValue,
                                      PHAssetMediaType.video.rawValue) // imageとvideoのみ取得
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending:false)] // 作成日時で降順でソートletfetchResult= PHAsset.fetchAssets(with:fetchOptions)

AttachmentInputでの使用例

端末に保存されている画像の変更を反映する

端末に画像が追加/削除されたり変更が加えられた場合、それらの変更を検知したい場合はPHPhotoLibraryChangeObserverを用いることで取得できます。

classReceiverClass:NSObject {
    varfetchResult:PHFetchResult<PHAsset>? =nilfuncyourInitialization() {
        PHPhotoLibrary.shared().register(self)
    }
    funcfetch() {
        self.fetchResult = PHAsset.fetchAssets(with:PHFetchOptions())
    }
}
extensionReceiverClass:PHPhotoLibraryChangeObserver {
    funcphotoLibraryDidChange(_ changeInstance:PHChange) {
        ifletfetchResult=self.fetchResult, letchangeDetails= changeInstance.changeDetails(for:fetchResult) {
            letnewFetchResul= changeDetails.fetchResultAfterChanges
        }
    }
}

AttachmentInputでの使用例 registerPHPhotoLibraryChangeObserver

iCloudに保存されている画像を取得する

iCloudに保存されている画像はPHImageRequestOptionsisNetworkAccessAllowedtrueにすると取得できます。

letimageRequestOptions= PHImageRequestOptions()
imageRequestOptions.isSynchronous =false
imageRequestOptions.isNetworkAccessAllowed =true
imageRequestOptions.progressHandler = { (progress, error, stop, info) inifleterror= error {
        // エラーreturn
    }
    // progress 0.0 ~ 1.0 でダウンロードの進捗を確認できます。
}
imageManager.requestImageData(for:phasset, options:imageRequestOptions){ (imageData, _, _, _) inifletimageData= imageData {
        // 画像取得
    }
}

AttachmentInputでの使用例

PHAssetからファイル名、ファイルサイズ、ファイルの保存されているパスを取得する

ファイル名を取得するには PHAssetResource.assetResources(for: phAsset)PHAssetResourceを取得することで.originalFilenameから取得することが可能です。
非公式の方法ですが、resource.value(forKey: "fileSize")でファイルサイズ、resource.value(forKey: "fileURL")でファイルパスを取得できます。
PHAssetResource.assetResources(for: phAsset)は処理が重いため必要なassetに対してのみ非同期で行うことをお勧めします。

※ valueで情報を取得する方法はPHAssetResourceの公開仕様ではないので、今後も使えるかは保証できません。
PHAsset#requestContentEditingInput(with:completionHandler:)でも情報を取得することもできますが、 画像編集のセッションを開始するため情報を取得するだけにしてはメモリも食うし扱いにくいです。

DispatchQueue.global().async {
    letresources= PHAssetResource.assetResources(for:phAsset)
    ifletresource= resources.first {
        letfileName= resource.originalFilename
        letunsignedInt64= resource.value(forKey:"fileSize") as? CLong
        letsizeOnDisk= Int64(bitPattern:UInt64(unsignedInt64!))
        letfileURL= resource.value(forKey:"fileURL") as? URL
    }
}

AttachmentInputでの使用例

AttachmentInputでは使っていないですが、iCloudに保存されていて取得にネットワークが必要な場合はlocallyAvailableをチェックすると判定できます。
こちらも非公式の方法なので今後も使えるかは保証できません。

resource.value(forKey:"locallyAvailable")

PHImageManagerのオプションの組み合わせによる挙動の違い

PHImageManagerのオプションで挙動を理解できずに一度は苦しむresizeModedeliveryModeがあります。
複数回段階的にロードされたり取得される画像のサイズが指定したものと違うものが返ってきたり、この二つのオプションの組み合わせによって直感では理解しがたい動きをします。
ドキュメントを読んでも細かいサイズの違いなどは読み取ることが難しいです。こちらは先人の知恵で素晴らしいマトリクスが公開されていましたので転載させていただきます。

ImagePickerControllerからphAssetを取得する

ImagePickerCellDelegateimagePickerController(_:didFinishPickingMediaWithInfo:)でImagePickerから画像や動画を選択した際の情報を取得できます。ここからphAssetを得るにはiOS11以降とそれより前で実装が変わります。
iOS11以降はinfo[.phAsset]で一発で取得できます。iOS10以下の場合はinfo[.referenceURL](Deprecated)からURLが取得できるので、fetchAssets(withALAssetURLs:options:)で取得することができます。

funcimagePickerController(_ picker:UIImagePickerController, didFinishPickingMediaWithInfo info:[UIImagePickerController.InfoKey : Any]) {
    varphAsset:PHAsset? =nilif #available(iOS 11.0, *) {
        phAsset = info[.phAsset] as? PHAsset
    } else {
        ifleturl= info[.referenceURL] as? URL {
            letfetchResult= PHAsset.fetchAssets(withALAssetURLs:[url], options:nil)
            phAsset = fetchResult.firstObject
        }
    }
}

AttachmentInputでの使用例

現場からは以上です!良いPhotosライフを!


「大人の体験入部」でNecoプロジェクトを体験してきました

$
0
0

こんにちは。kintoneメンテチームの石井です。

サイボウズでは、cybozu.comのアーキテクチャ刷新プロジェクト「Neco」を実施しています。 今回、サイボウズの「大人の体験入部」の制度を利用し、Necoプロジェクトの体験入部をしてきたので、その内容をレポートします。

本記事では「大人の体験入部」および「Neco」プロジェクトを紹介します。 Necoプロジェクトで体験したタスクについては、以下の記事に記載していますので、興味があれば参照いただければと思います。

そもそも「大人の体験入部」って何?

「大人の体験入部」とは、サイボウズのキャリア形成・研修制度の一つで、気になる部署の仕事を一時的に体験することができる制度です。 期間は1日から最大3ヶ月、日本国内だけでなく海外拠点の体験入部も可能です。 サイボウズでは、自分のキャリアは自分で決める文化があり、それをサポートするための制度の一つですね。

希望理由は人それぞれですが、例えば以下のような理由で「大人の体験入部」が利用されています。

  • あの部署に異動してみたいけど、やっていけるか不安なのでちょっと体験してしてみたいな。。
  • あの部署のノウハウを学んで、自分の業務に活かしたいなぁ。
  • てか、あの部署がどんなことをしているのか知りたい!

もちろん、所属部門や受入先部門との調整は必要になりますが、ほとんどの場合で希望どおり体験入部が実施されているようですね。

Necoプロジェクト体験入部

そんな大人の体験入部ですが、今回は、大阪から東京オフィスへ、3週間ほどNecoプロジェクトへ体験入部をしてきました。

経緯的なものを説明しますと、昨年末の面談で当時のボスに、 インフラ周りに興味があってチャンスがあればNecoプロジェクトとかやってみたい、みたいな事を言いました。 すると、あれよあれよと言う間にNecoプロジェクトのリーダー@ymmt2005さんと面談することになり、今回の体験入部が決まりました。

面談当時まだ入社3ヶ月程度(今は半年くらい!)だったのですが、こんなワガママみたいな事聞いてもらっていいのだろか、と素直に驚きました。

それでNecoプロジェクトって?

「Neco」プロジェクトについてはこのブログ(Cybozu Inside Out)でもいくつか記事が書かれているので、ご存知の方もいると思いますが、 現行のcybozu.comのインフラ環境はいろいろつらみがあるので、今どきの技術を取り入れてNoOpsなインフラを作っちゃおう!といったプロジェクトです。 Necoプロジェクトで開発しているプログラムは、OSSとして公開しているのでぜひご確認ください。

現在は、ネットワーク設計やKubernetesをベースとしたクラスタ管理のための基本的な機能の開発が完了し、実運用で必要になる機能やモニタリングツールを拡充している段階です。 1stターゲットはElasticsearchで、現行のcybozu.comで動いているサービスを、段階的にNecoに移行する計画になっています。

まだまだやることがたくさんあるので、We are hiring!といった状態です。

モブプログラミング、はじめました (注: 前からやってます)

手前みそかもしれませんが、Necoプロジェクトは結構難しいことをやっています。

Kubernetesをベースに、システム運用を妥協なく自動化し、例え障害が起きたとしても事前に宣言(設定)しておいた状態に自動で復旧してくれるようなインフラを目指しています。 そのためには、クラスタのネットワーク構成からアプリケーションの状態まで、きちんと理解して制御してあげる必要があります。

つまるところ、覚えることがとても多いです。(いや多かった。。)

そのため、体験入部が始まる前は、希望したものの大丈夫だろうか?と不安があったのですが、実際に体験入部を始めてみると、

  • 新しい人向けのチュートリアル
  • モブプログラミングによるOJT
  • 過去の調査レポート

などがあり、気合を入れれば何とかなるだろうな、といった気持ちになりました。

Necoプロジェクトの人たちも、まず知識をつけることが最優先と考えてくれていて、また優しい人が多い (これはNecoプロジェクトというよりもサイボウズメンバー全般ですね)ので、わからないことは聞けばいろいろと教えてくれました。

今回の体験入部でネットワークやKubernetsなど自分の知らない領域のことをガッツリ学べたので、エンジニアとして良い体験をさせてもらったな、と言うのが正直な感想です。 まだまだ、いろいろな事にチャレンジしていきないな、と思える3週間となりました。

(本記事はここまで、体験入部中に行なったタスクについてはこちらをどうぞ!)

おわりに

今回の記事では、サイボウズの「大人の体験入部」の制度と、「Neco」プロジェクトについて紹介しました。

サイボウズは、扱う製品は少なめですが、フロントエンドからサーバサイド、インフラまわりまで、全て自社開発をしており技術的な幅を持った会社だと思います。 また、文化的にも制度的にも、意志さえあればチャレンジできる環境があります。

チャレンジしたいなぁ、今の会社窮屈なんだよなぁ、と思っている、そんなあなたに、つ[We are hiring!]

KubernetesロードバランサーのMetalLBを導入した話(Necoプロジェクト体験入部)

$
0
0

こんにちは。kintoneメンテチームの石井です。

サイボウズでは、cybozu.comのアーキテクチャ刷新プロジェクト「Neco」を実施しています。 今回、サイボウズの「大人の体験入部」の制度を利用し、Necoプロジェクトの体験入部をしてきたので、その内容をレポートします。

本記事では、体験入部中に行なったタスクの紹介をします。 「Neco」プロジェクトや「大人の体験入部」について知りたい方は、以下の記事を参照ください。

こんなことしてました

Necoでの体験入部中、いくつかのタスクを体験(勉強?)させてもらいました。 今回はその中の一つ、ロードバランサー(MetalLB)の導入について説明します。

Kubernetesとネットワーク

はじめに、Kubernetesとネットワークについて少しだけ説明しておきます。

Kubernetesとは、分散システム上で、コンテナ化したアプリケーションの管理をしてくれるシステムです。 システムのあるべき姿を宣言(設定)しておくと、Kubernetesが自律的にコンテナのデプロイやスケール、障害の復旧もしてくれる優れもので、 これにより、大規模なデータセンターでも、少人数で管理ができるようにしようというものです。 とても便利なシステムではありますが、Kubenetesクラスタ(Kubenetesが管理するサーバのまとまりのこと)を構築するには、 いろいろとやらなければいけないことがあり、その1つにネットワークの設定があります。

Kubernetesは、データセンターのような多くのサーバからなる分散システム上での動作を想定しているのですが、 サーバ間の通信方法はデータセンター内のネットワーク構成に依存するため、Kubernetesとしての標準実装を提供できません。

例えば、Pod(Kubernetesでのアプリケーションの動作単位)を作成する場合、 PodへのIPアドレスの割り当てや、経路情報の広報が必要になるのですが、 IPアドレスの体系や、通信経路の冗長度、ルーティングプロトコル等は、データセンター毎に異なるので、 利用者(Kubernetesクラスタを構築する人)が環境に合わせた設定をしなければいけません。

そのため、利用者側で具体的な通信方式をプライグインとして追加できるように、 Kubernetesでは、通信に必要な要件とインターフェースだけを定義しています。

GCPやAWSといったIaaSでは、クラウドベンダーがプラグインを作成してくれているので、 サービス利用者側がこういったことを意識することはあまりありません。 しかし、Necoでは、自社データセンター内にKubernetesクラスタを構築するため、プラグインの選定や実装をする必要がありました。

Necoのネットワーク

Necoのネットワークについては、このブログ(Cybozu Inside Out)でもいくつか記載しています。

詳細は、上記記事を参照いただければと思いますが、これまで以下のような事を実施してきました。

  • CLOSアーキテクチャを採用し、East-Westトラフィック(同クラスタ内のサーバ間の通信)が増大してもスケールしやすいネットワークの設計
  • BGPを使用して、ネットワーク機器ベンダの独自機能に依存しない、高信頼なネットワークの実現
  • 大規模クラスタでの経路情報の増大を防ぐために、ネットワークプラグインcoilの実装

これらにより、Kubernetesクラスタ内での通信は問題なく動作するようになりました。 簡単に表現すると、以下の図のようなイメージです。ただし、まだ外部からの通信の対応はできていませんでした。

内部通信
内部通信

Kubernetesのロードバランサー

Kubernetesでは、負荷分散や冗長化のため、Serviceと呼ばれる単位で複数のPodをひとまとめに扱うことができます。

ServiceにはClusterIPLoadBalancerなどのタイプがあり、うちLoadBalancerタイプが外部通信の負荷分散用とされています。 例えば、レプリカ数3でnginxのコンテナを動かす、といったLoadBalancerタイプのServiceを設定した場合、Kubernetesが3つのPodでうまいこと負荷分散してくれます。

クラスタ外部からアプリケーションにアクセスする場合は、LoadBalancerタイプのServiceに割り当てられた公開用IPアドレス(External IP)に対してアクセスします。 すると以下の図のように、ロードバランサーによって動作中のPodにアクセスが振り分けられます。

k8sロードバランサー概要
k8sロードバランサー概要

ただし前の節で説明したように、Kubernetesではネットワークに関する標準実装は提供していません。 ロードバランサーについても、外部からExternal IPへのルーティングや、各Podへのアクセスの振り分け方法は、利用者(Kubernetesクラスタを構築する人)側でプラグインの選定や実装をする必要がありました。

そこで今回のタスクでは、社内(現行のcybozu.com)から接続する際のロードバランサーの実現性検証と各種設定を行いました。

MetalLB

Necoでは、以下の要件から社内向けロードバランサーとして、MetalLBというロードバランサーを使うことを計画しており、 今回のタスクで、MetalLBをNecoに導入する場合ための実現性検証と各種設定を行いました。

  • 自社データセンターのベアメタル環境で利用する
  • 経路制御は BGP で行う

MetalLBとはベアメタル環境で使用できるKubernetesのExternal Load Balancerの実装の一種です。 Googleによって開発されたシンプルなロードバランサーで、LoadBalancerタイプのServiceに対する公開用IPアドレス(External IP)の割り当てと、 External IPに対する経路情報の広報、といったの2つの機能を持ちます。

少し実装に踏み込んだ話をしますと、MetalLBはControllerSpeakerの2種類のモジュールで構成されており、 External IPの割り当てと、経路情報の広報は次の図のように実施されます。

MetalLBの経路情報の広報
MetalLBの経路情報の広報

そして、外部からのアクセスがあった場合、設定された経路情報をもとに、ルータやipvsが接続先を調整してくれます。 (そのため、MetalLB自体がロードバランスをしている感じはないですね。)

MetalLBのロードバランス
MetalLBのロードバランス

Necoに入れたらこうなった

Necoでは、各Node上でBIRDというルーティングソフトウェアが動作しており、BGPで経路の交換を行なっています。 そこで、MetalLBからの経路の広報先を自NodeのBIRDに設定し、BIRDから経路の広報を行うようにしました。

下の図は、MetalLBから外部までどのように経路が広報されるか(≒通信経路)を示した図になります。 各Nodeで動作するMetalLBから、BIRD、各種Switchと、経路情報が広報されていき、 外部からKubernetes上で動作するPodにアクセスできるようになりました。

Neco + MetalLB
Neco + MetalLB

外部からアクセスされた場合、以下の図のようにルーティングされます。 各種Switchから各NodeまではECMP(Equal-Cost Multi-Path routing)でルーティングされます。 そして、各Nodeに到達したパケットは、ipvsにより各Podに振り分けられるようになりました。

Necoルーティング
Necoルーティング

MetalLB導入時の問題点

このように書くとすんなりできたように見えますが、以下のような問題がありました。

externalTrafficPolicy=Localを指定するとパケットがロストする(kube-proxy v1.13の不具合)

LoadBalancerタイプのServiceにはexternalTrafficPolicyというオプションがあり、kube-proxyでアクセス先のPodを振り分ける際に、 クラスタワイドに(Nodeをまたいで)アクセス先を振り分けるか、それともノードローカルのPodにだけ振り分けるか、を設定することができます。

NecoではNode間の通信を少なくするためexternalTrafficPolicy=LocalとしてノードローカルのPodにだけアクセスを振り分ける設定にしたのですが、 この時外部からPodに対してアクセスすると一定の割合でパケットがロストするといった事象が発生しました。

Necoでは、Kubernetes v1.13を使用しているのですが、このバージョンのkube-proxyには 以下の条件でパケットがロストしてしまうという不具合があり、それが原因でした。

  • kube-proxyをipvsモードで動作させる
  • Serviceの設定でexternalTrafficPolicy:Localを指定する

この不具合はKubernetes v1.14で改修されていたため、修正パッチをあてて対応しました。

https://github.com/kubernetes/kubernetes/issues/71596

kube-proxyのオプション指定誤り(kubeletとkube-proxyの両方で--hostname-overrideを指定する)

もう一つが、kube-proxyの--hostname-overrideオプション指定の誤りです。

kubelet(Podを管理するモジュール)は、デフォルトでは/etc/hostnameの値をNode名として扱うのですが、 Node名を別に設定したい場合は--hostname-overrideオプションでNode名を指定することができます。

いままで、kubeletだけがNode名を使用していると認識していたのですが、 externalTrafficPolicy=Localを指定したLoadBalancerタイプのServiceで、ノードローカルなPodがあるかどうかを判定する際に、 kube-proxyがNode名を参照していることが分かりました。

Necoでは、kubeletだけ--hostname-overideオプションを指定していたため、kubeletとkube-proxyでNode名の判定で不整合が出てしまい動作しませんでした。

こちらはCKEを改修し、kube-proxyの起動時に--hostname-overideオプションを指定するようにしました。

最終的にどうなった?

MetalLBを導入することで、外部からNecoのKubernetesクラスタにアクセスできるようになりました。

Kubernetesクラスタ上で動作するServiceにグローバルIPアドレスを割当て、 現行のcybozu.com(社内データセンター)及びInternet(社外)からアクセスできることを確認しました。

おわりに

今回の記事では、「大人の体験入部」として「Neco」プロジェクトで行ったタスクの一部を紹介しました。

Necoプロジェクトでは、まだまだやらなければいけないことが沢山あり、一緒に働いてくれる方を募集しています。 今回の内容に興味がある、やってみたい、そもそも俺だったら一瞬で終わらせてやるぜ!っといった方がおりましたら、 ぜひご応募ください。We are hiring!

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

$
0
0

こんにちは、サイボウズ・ラボの星野です。

今回は2019/03/22に開催された第8期サイボウズ・ラボユース成果発表会の模様を紹介します。

サイボウズ・ラボユース

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

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

今期はラボユース生4名が卒業しました。

発表会のレポート

発表順に紹介します。

増田 健太さん「制約を用いた作図言語Pita」

増田さん発表中

増田さんは2017年秋からで2018年夏までラボユース活動をしました。メンターは星野です。Pita は専用の言語で図形やその制約条件を定義することで、最適化問題に変換して制約を守った図形を探索し、ベクター画像として出力するソフトウェアです。絵心がない私から見ると、規則性をもった図形をプログラムとして記述でき、制約によってより簡単に図形の関係を表現できるのは大きな魅力だと思います。昨年の発表では Pita の内部の解説がメインでしたが今回はデモを中心とした発表でした。 ドキュメントとデモが用意されており、気軽に試すことができます。PitaはC++言語で書かれているソフトウェアですが、EmscriptenでJavaScriptに変換してデモとして気軽に動かせるようにしたそうです。ソースコードはGitHubにあります。発表資料はこちらです。

田辺 敬之さん 「トランザクション処理における並行性制御法の評価」

田辺さん発表中

田辺さんは2018年春からラボユース活動をしました。メンターは星野です。彼はラボユース活動を通して、最近の論文として発表された最先端の並行性制御(Concurrency Control)手法を複数再実装し、性能評価した結果得られた知見を発表してくれました。発表によると、MVCC (Mutli-version Concurrency Control) と 1VCC (Single-version Concurrency Control) はワークロードの競合度合いなどによって得手不得手があることや、メニーコアアーキテクチャにおいては、CPUキャッシュを汚さない invisible read のメリットがあることなどが、明らかになったそうです。プロトタイプのソースコードはGitHubにあります。

河原 颯太さん 「NICのデバドラ開発」

河原さん発表中

河原さんは2018年夏からラボユース活動をしています。メンターは光成さんです。彼はネットワークプロトコルスタックを作りたくてラボユースに応募しました。今はNICを自由にいじるべくLinuxカーネルモジュール作成にチャレンジしていますが、まだ道半ばのようです。発表の中で、最近は DPDK だけでなく XDP (たぶん eXpress Data Path) が劇熱(ゲキアツ)だそうですが、将来的にはFPGAにも取り組みたいとのことです。高速なネットワーク処理に興味があるのですね。確かに昨今FPGAでネットワーク処理をアクセラレートする研究はよく見かけますので、楽しみです。河原さんは今回で卒業ではなく、ラボユース活動を続ける予定です。ソースコードはmoduleおよび kidnetにあります。

濱田 槙亮さん 「glibc heapの可視化によるexploit作成の補助ツール「exp-heap」」

濱田さん発表中

濱田さんは2018年春からラボユース活動をしました。メンターは光成さんです。大学ではFPGAや省電力プロセッサの研究をしていて、ラボユースではCTFのBinary Exploitジャンルで問題を早く解くためのツールとしてexp-heapの開発に取り組みました。exp-heapはglibcのmalloc/calloc/free/reallocをフックしてログを出し、ヒープアルゴリズムを再現してメモリ構造の状態変化を追うツールです。glibcバージョンが変わるとアルゴリズムも変わるので追随する必要がありますし、挙動を完全に再現するのは難しいようですが、構造を追うのには役立つとのこと。現在は完全にCUIソフトウェアですが、良いUXをつけて便利にしたいというモチベーションもあるようです。ソースコードはGitHubにあります。発表資料はこちらです。

渡邊 大記さん 「階層独立性の高いプロトコルスタックの設計と開発」

渡邊さん発表中

渡邊さんは2018年春からラボユース活動をしました。メンターは光成さんです。大学ではネットワーク理論の研究をしていて、実装ドリブンの活動もしたくてラボユースに応募しました。発表では開発に取り組んだexlayのモチベーションや現状について語ってくれました。ネットワーク階層間の独立性を高くして様々なプロトコルを気軽に作って試せる仕組みが欲しくて、x-kernelを今風にリアレンジしexlayを設計したそうです。カーネル空間で動くソフトウェアがゴールですが、PoCとしてまずユーザーランドで作ってみて動かしています。ソースコードはGitHubにあります。

卒業式

発表会の終わりに、卒業するラボユース生ひとりひとりに対して、希望を反映した記念品とメンターのコメント付き卒業証書が手渡され、その場でコメントをもらいました。 私の個人的な感想ですが、皆さんそれぞれがそれぞれの方法でラボユース制度を活用してくれたと思います。

懇親会

懇親会の様子

懇親会はラボユースOBの方々も参加してくれて、発表会の内容だけでなく、現在興味のある技術などについて議論が白熱しました。ラボユース経験者やラボメンバーに話を聞ける機会ですので、ラボユース活動に興味のある方は是非とも次の発表会にお越しくださいね。

おわりに

本記事は第8期サイボウズ・ラボユースの成果発表会の内容を紹介しました。サイボウズ・ラボユースは2019年度も募集を予定していますので、応募を検討されている方は少しお待ち下さい。

他のチームを体験する「モブプロ開発合宿」を開催しました

$
0
0

開発合宿の集合写真

こんにちは!開発本部の外松(@toshi-toma)です。

サイボウズでは3年ぶりに開発合宿を開催しました!

今年の合宿は、複数のプロダクトチームや特定分野のエキスパートチームを含めた31名の方に参加してもらいました。 合宿会場は、箱根湯本の「ホテルおかだ」です。

今回の合宿は、モブプログラミングを活用して他のチームを体験するモブプロ開発合宿だったのですが、こんな効果がありました。

  • 他のチームのノウハウやスキル、仕事の進め方が一気に知れる
  • 普段の業務では関わりが少ないメンバーと一緒に開発することで人となりを知れる
  • 特定分野のエキスパートの力を借りて、なかなか進めれなかった改善が実現

個人的にとても良い合宿だったので、その様子を紹介します!

合宿のテーマ

合宿のテーマは「となりのチームを知る」です。

サイボウズでは、今年から新組織体制となり従来のマトリクス組織から各チームがフラットに並ぶ体制になりました。 blog.cybozu.io

各チームが自己組織化して動くことができる一方で、チームを跨いだノウハウ共有は意識しないと頻繁には行わなくなります。
そこで、開発合宿では普段の業務では関わりが少ない他のチームの「仕事の進め方」や「人」、「ノウハウ」を知るという事をテーマに設定して開催しました。

モブプログラミングならできる

いざ他のチームの開発を体験しようと思っても、チームによって使っている技術やプログラミング言語が違います。
サイボウズでは去年から、ほとんどのチームでモブプログラミングでの開発が盛んに行われているので、合宿でもモブプログラミングを基本としました!
これで技術や言語が違う問題は解決でき、いろいろなチームから集まったメンバーによってノウハウの共有がスムーズに行えます。

モブプログラミングの様子1
モブプログラミング

モブプログラミングならいきなり他のチームに参加しても開発できます!

そしてモブプログラミングのいいところは、「完成した時の喜びをみんなで共有できる」ところですよね。

モブプログラミングの様子2モブプログラミングの様子3モブプログラミングの様子4

他のチームの開発を体験してノウハウを共有

当日は、各チームが自由にテーマを設定して「製品サービスの向上、生産性向上や日々の課題を解決」するKAIZEN開発に、そのチームを体験してみたい人が自由に加わるという流れで進めました。

[f:id:cybozuinsideout:20190328170731p:plain=各プロダクトのチームメンバーが集まっている様子]

例えば、kintone開発チームのメンバーが中心になって、Garoon開発チームやモバイルチームのメンバーとkintoneの開発をしてみるといったイメージです。

サイボウズは開発拠点が複数存在するので、あまり話したことがないメンバーもいて、自己紹介から始めているチームも多かったです。

合宿中の様子1

また、開発に入る前に自分たちのチームの進め方を紹介してから始めることも多く、「その進め方いいね!うちでもやってみよう」という声も聞こえてきました。

合宿中の様子2

一見自分にとっては普通に思えることも、他チームの人からすると大切な新しい気づきになるので、積極的に自分たちのチームがどんな風に開発をしているのか紹介してもらいました。

合宿中の様子3合宿中の様子4

学んだこと、感じたこともみんなで共有

合宿中は、ホワイトボードにFun/Done/Learnボード(振り返りの手法)を設置して、随時学びや感想などを付箋で貼ってもらいました。

Fun、Done、Learnにあたることを付箋に書いて貼っています

「自分以外の人がどんな風に感じたのか?」「自分では気付けなかった他の人の学び」を共有することで学びのサイクルを作れます。 あとは、楽しかったことをみんなでワイワイ付箋で貼っていくのは、単純に楽しいですよね。

合宿開始時はまだ学びが少なかったですが、合宿終了時にはたくさんの学びや気付きがホワイトボードに貼られている様子

いつものチームとは違うメンバーと、普段なかなか行えなかった技術的課題に取り組んだり 他のチームを体験することで、たくさんの「Fun(楽しかったこと)」「Done(やったこと)」「Learn(学んだこと)」が集まりました!

合宿の様子

合宿の様子をいくつか写真で紹介します。

足湯をしながらも開発しています
施設はWifi完備なので、足湯をしながらも開発
くじ引きで部屋割りを決めています
部屋割りはくじ引きで決めました
皆で食事をしているところです
ご飯がとても美味しかったです
すごい開発をやってそうな雰囲気ですがやってない様子です
すごいやってそう(やってない)
大きい画面でモブプロをしています
大きい画面でモブプロ
最後はFun、Done、Learnボードで合宿のふりかえりです
最後はFun/Done/Learnボードで合宿のふりかえり

合宿後に成果発表会

合宿後は、社内で成果発表会を開催しました!
合宿中に開発した機能のプロトタイプ紹介やデモ、他のチームを体験して感じたことなどを社内に向けて共有してもらいました。

社内で成果発表会をしている様子1社内で成果発表会をしている様子2

おわりに

モブプロ開発合宿いかがでしたでしょうか?
合宿という同じ場所・同じ時間を過ごすことで、普段なかなか行えないことも気軽に行えます。そして、みんなで取り組む活気があってとても楽しかったです!
合宿後、私のチームでは他のチームのメンバーに相談する頻度が確実に増えたなと思いました。

たぶん来年も合宿を開催すると思います。今後もサイボウズのエンジニア全体で協力しあって、開発を行えるように色々な取り組みを実施しようと思っています。

git-buildpackageを用いたdebパッケージ管理方法の紹介

$
0
0

こんにちは。サイボウズ・ラボ アルバイトの宮川(@miyagaw61)です。

今回は、Gitでソースコードを管理するソフトウェアにおいて、git-buildpackage(gbp)コマンドを使って複数のLinuxディストリビューションやそのバージョン毎に効率良くdebパッケージ化を行い、メンテナンスする方法について紹介したいと思います。

環境

Ubuntu Server 16.04 LTSで作業していますが、 Ubuntuの14.04や18.04でも問題なく動くと思います。

事前知識と準備

deb-version

deb-versionとはdebianパッケージで用いられているバージョン番号形式のことで、{アップストリームバージョン}もしくは{アップストリームバージョン}-{リビジョン}という形式で表されます。 アップストリームバージョンは英数字(A-Za-z0-9)と記号(.+:~)が使用可能で、先頭は数字である必要があります。 一般的には「X.Y.Z」という形式で表されることが多く、XはMajor番号、YはMiner番号、ZはPatch番号と呼ばれます。 それぞれは次の意味を持っています。

  • Major番号:後方互換性がない変更の時にインクリメントする番号
  • Minor番号:後方互換性がある変更の時にインクリメントする番号
  • Patch番号:バグ修正の時にインクリメントする番号

リビジョンは英数字(A-Za-z0-9)と記号(+.~)が使用可能で、付けるか否かは自由です。 一般的には先頭に数字が置かれます。そのあとに~+が置かれ、任意の文字列が置かれることもあります。 アップストリームバージョンもリビジョンも、いずれも以下のようなルールを持っています。 0.0 < 0.5 < 0.10 < 0.99 < 1 < 1.0~rc1 < 1.0 < 1.0+b1 < 1.0+nmu1 < 1.1 < 2.0

詳しくはこちらを参照ください。

上流と下流の用意

以下の二つのリポジトリを用意しておいてください。

  • 上流リポジトリ: debianディレクトリの無い大元のソースコード管理リポジトリです。このリポジトリは自分ではコントロールできないものとします。 例ではgit@github.com:miyagaw61/test.gitを使います。後で中身は作りますので、最初は空で構いません。
  • 下流リポジトリ:debianディレクトリの差異を管理するリポジトリです。 例ではgit@github.com:miyagaw61/pkg-test.gitを使います。上流リポジトリ同様、最初は空で構いません。

リポジトリ名やコミットメッセージなどは、適宜変更してください。

作業の流れ

  1. 下流リポジトリを作成してdebパッケージをビルドできるようになるまで
  2. debパッケージ側で管理するパッチの更新
  3. 上流の変更への追従

新規作成・初期設定

では、さっそく始めていきましょう。 上流リポジトリが無ければ何も始まりません。 ここでは、誰かが上流リポジトリで新規バージョンをリリースしたものとします。その内容は以下のようなものであるとします。

$ git clone git@github.com:miyagaw61/test.git
$ cd test
$ echo "echo hello" > hello.sh
$ git add hello.sh
$ git commit -m "Add hello.sh"
$ git tag upstream/1.0.0
$ git push origin master
$ git push origin upstream/1.0.0
$ cd ..

次に、予め作成しておいた空の下流リポジトリをクローンします。

$ git clone git@github.com:miyagaw61/pkg-test.git
$ cd pkg-test

以降、断わりがない限り下流リポジトリでの作業となります。 上流リポジトリをfetchします。

$ git remote add upstream git@github.com:miyagaw61/test.git
$ git fetch upstream
$ git checkout -b upstream upstream/master

任意のDebian系ディストリビューションの任意のバージョンにおいて、debianディレクトリ以下やソースコードなどをそのバージョンに合わせて管理するための、専用のブランチを作成します。

$ git checkout -b xenial

今回はxenial(Ubuntu16.04)環境で解説していくため、名前はxenialとしました。

次に、debianディレクトリを作成します。 どんな方法で作成しても大丈夫ですが、今回は簡単のためdh_makeを使用します。パッケージクラスはsingleに設定します。

$ dh_make --createorig -p test_1.0.0 -s -y

ここでのtest_1.0.0は、{パッケージ名}_{アップストリームバージョン}の形式にしてください。 このとき、{アップストリームバージョン}は先ほどのupstreamに付けたタグと同じにしてください。

debian/{パッケージ名}.installファイルを作成します。 このファイルでインストールするファイルとインストール先のパスを指定します。

$ echo "hello.sh usr/bin" > debian/test.install

スペース区切りで、左側にインストールするファイルをパッケージのディレクトリからの相対パスで、右側にインストール先のパスをルートディレクトリからの相対パスで記述します。 こちらに書いてあるように、DebianのPolicyにより/usr/local以下にパッケージをインストールすることは禁止されています1

変更をadd・commitします。

$ git add debian
$ git commit -m "Add debian directory"

ビルドが成功するか確認します。

$ gbp buildpackage -us -uc --git-ignore-new

-us -ucオプションを使用すると署名を行いません。 --git-ignore-newオプションを使用するとstagingされていないファイルが存在していてもビルドできるようになります。

ビルドが成功したら、インストールして実行してみます。

$ sudo dpkg -i ../test_1.0.0-1_amd64.deb
$ hello.sh

"hello"と出力されていれば正常です。

ビルドすると、debianディレクトリ以下に新たにファイルがいくつか生成されます。 無事ビルド・インストール・実行の全てが正常に行えることを確認したら、まずはビルド時に生成されたファイルを全て削除してビルド前の状態に戻しましょう。

$ git status
$ rm -rf debian/{debhelper-build-stamp,files,test.debhelper.log,test.substvars,test}

ビルド直前のデータはコミットされているので、rm -rfを使う代わりに以下のコマンドでも消せます。

$ git clean -df

PbuilderやCowbuilderなどを使用してビルドしたい方は、ビルド時に--git-pbuilderオプションを追加で使用してください2。 事前に以下の方法でベースイメージ(例:/var/cache/pbuilder/cow.base)を作成しておく必要があります。 ~/.pbuilderrcDIST環境変数の内容は適宜書き換えてください。

$ vim ~/.pbuilderrc
$ cat ~/.pbuilderrc
COMPONENTS="main restricted universe multiverse"
$ DIST=xenial BUILDER=cowbuilder git-pbuilder create

そして、現在のバージョンのタグを作成します。 現在のバージョンとは、現在のchangelog(debian/changelogファイル)に記述されている最新のバージョンのことです。 次のコマンドで、現在のバージョンのタグを自動で作成できます。(--git-tag-onlyにより実際のビルドはしません)

$ gbp buildpackage --git-tag-only --git-ignore-branch

現在のchangelogの最新バージョンは"1.0.0-1"なので、"debian/1.0.0-1"というタグが自動で作成されました。 上流と下流のタグはそれぞれ、次に示す命名規則に従っている必要があります。

  • 上流に付けるタグはupstream/{アップストリームバージョン}という表記でなければならない(--git-upstream-tagで変更可能)
  • 下流に付けるタグはdebian/{アップストリームバージョン}もしくはdebian/{アップストリームバージョン}-{リビジョン}という表記でなければならない(--git-debian-tagで変更可能)
  • 下流のタグに含まれるバージョン文字列はchangelogに記述されているバージョン文字列と完全に一致していなければならない

--git-upstream-tag--git-debian-tagなどのオプションを毎回指定するのが面倒な場合は、debian/gbp.confに設定を記述することで省略することができます3

無事タグの生成を済ませたら、タグをpushしましょう。

$ git push origin debian/1.0.0-1

pristine-tarについて

orig.tar.gzファイルは上流メンテナが提供するソースコードと同じ内容を含むアーカイブです。orig.tar.gzファイルは誰でも生成できるのですが、上流メンテナと全く同じコードをビルドしたとしても、環境によってはファイルとしては異なるものになってしまうことがあります。しかし、上流メンテナと全く同じorig.tar.gzを生成しなければいけない場合があります。そういう時は、pristine-tarを使用してください。 pristine-tarは、上流メンテナと全く同じorig.tar.gzファイルを再生成するためのpristine-tarデータの作成や、pristine-tarデータを用いて上流メンテナと全く同じorig.tar.gzの生成などを行うことができるコマンドです。 gbp buildpacakgeコマンドには、--git-pristine-tar--git-pristine-tar-commitという二つのオプションがあります。これらは、pristine-tarを内部で使用しており、上流メンテナと全く同じorig.tar.gzを用いてdebパッケージ化する際に役立ちます。 組み合わせごとの詳細な内部処理を解説します。以下の4パターンがあります。

  1. --git-pristine-tar--git-pristine-tar-commitも指定しなかった場合: changelogからアップストリームバージョンを抽出します。それを対応する上流のタグに変換し(変換方法は--git-upstream-tagオプションで指定可能)、そのタグが付いたコミットを探し、そのソースコードからorig.tar.gzを生成します(既にorig.tar.gzを持っていたら生成せずにそれを使います)。そして、そのorig.tar.gzを用いてdebパッケージ化します。

  2. --git-pristine-tar-commitのみを指定した場合: aの手法でビルドすると同時に、ビルドするバージョンのpristine-tarデータがpristine-tarブランチに存在していない場合、ビルドに使われたorig.tar.gzからpristine-tarデータを生成し、pristine-tarブランチにコミットします。

  3. --git-pristine-tarのみを指定した場合: changelogからアップストリームバージョンを抽出します。そのバージョンのpristine-tarデータがpristine-tarブランチに存在していないとエラーになります。存在していたらそこからorig.tar.gzを再生成(その再生成したorig.tar.gzと既に持っているorig.tar.gzのハッシュ値が異なる場合はエラー)し、そのorig.tar.gzを用いてdebパッケージ化します4

  4. --git-pristine-tar--git-pristine-tar-commitを両方を指定した場合: changelogからアップストリームバージョンを抽出します。そのバージョンのpristine-tarデータがpristine-tarブランチに存在していれば--git-pristine-tarオプションのみ有効に、存在していなければ--git-pristine-tar-commitオプションのみ有効になります。

通常は、--git-pristine-tar--git-pristine-tar-commitの二つのオプションを併用してビルドする運用で問題無いと思われます5

パッチの更新

パッケージ管理者がdebianディレクトリ以外に存在するソースコードを修正したい場合は、オリジナルソースコードに対するパッチファイルを作成し、quiltコマンドで管理します。 そのパッチファイルはdebian/patchesに格納されます。 gbpを用いると、quiltを直接使わずにgitと連携してパッチを管理することができます。 debian/patches以下のパッチファイルは本記事ではquiltパッチと呼んで区別することにします。

先程作成した、特定バージョンにおいてのパッケージ管理用ブランチに移動します(ここではxenialブランチを指します)。

$ git checkout xenial

xenialブランチにいる状態でgbp pq importというコマンドを実行するとpatch-queue/xenialというブランチが自動で作成され、xenialブランチのdebian/patches以下にあるパッチファイルが全てコミットとして再現されます。 これらのpatch-queue/BRANCHをここではpatch-queueブランチと呼ぶことにします。 既に対応するpatch-queueブランチが存在する場合は作成ではなくgbp pq switchgbp pq rebase(詳細は後述)で移動する必要があります。

$ gbp pq import # もしくはgbp pq switchかgbp pq rebase

quiltパッチがひとつも存在しない状況では、図にすると次のようになります。

初期状態のブランチ

quiltパッチを作成,編集,削除したい時はpatch-queueブランチで開発します。 通常の開発と同様にファイルを編集し、commitします。

$ sed "s/hello/hello, world/g" -i hello.sh
$ git add hello.sh
$ git commit -m "Add world to hello.sh"

patch-queueブランチで開発

開発がひと段落したら、xenialブランチとpatch-queue/xenialブランチとの差分コミットをパッチファイルという形にしてxenialブランチへ送ります。 --commitオプションを付けることで、自動でxenialブランチへの変更コミットも行ってくれます。

$ gbp pq export --commit

patch-queueブランチをxenialブランチに統合

patche-queue/xenialブランチ上ではオリジナルソースコードへの変更がそのままgitコミットとして存在しますが、xenialブランチ上ではdebian/patches/以下のパッチファイルを追加するgitコミットとして存在します。 xenialブランチに戻ってきたら、changelogを更新します。 次のコマンドで、バージョン1.0.0-2+xenialとしてchangelogを更新できます。

$ gbp dch -N 1.0.0-2+xenial --commit --ignore-branch

変更前の changelog:

test (1.0.0-1) unstable; urgency=medium

 * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

 -- miyagaw61 <miyagaw61@unknown>  Fri, 08 Jun 2018 15:30:58 +0900

変更後の changelog:

test (1.0.0-2+xenial) UNRELEASED; urgency=medium

  * Add world to hello.sh

 -- miyagaw61 <miyagaw61@gmail.com>  Fri, 08 Jun 2018 15:54:26 +0900

test (1.0.0-1) unstable; urgency=medium

  * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

 -- miyagaw61 <miyagaw61@unknown>  Fri, 08 Jun 2018 15:30:58 +0900

--commitオプションは自動でcommitも行ってくれるオプション、--ignore-branchオプションはmasterブランチ以外でも更新できるようにするオプションです。 ちなみに、-Nオプションを付けなければ現在のバージョンにパッチを追加、-Rオプションを付けるとchangelog中のUNRELEASEDが自分のマシンのOSバージョン(ここではxenial)に書き換わり、リリースに向けた最終調整のためにエディタが起動します。

同じバージョンのタグも生成します。

$ gbp buildpackage --git-tag-only --git-ignore-branch

"debian/1.0.0-2+xenial"というタグが git リポジトリに追加されました。 masterブランチ以外でタグを生成する時は--git-ignore-branchオプションが必要です。

ビルドが成功するか確認します。

$ gbp buildpackage -us -uc --git-ignore-branch

現在のchangelogの最新バージョン名は"1.0.0-2+xenial"なので、対応するアップストリームバージョンは"1.0.0"になります。 そのため、今回は"upstream/1.0.0"のタグが指しているコミットに対応するソースコードを使ってビルドします。 ビルドが成功したら、インストールして実際に実行してみましょう。

$ sudo dpkg -i ../test_1.0.0-2+xenial_amd64.deb
$ hello.sh

"hello, world"と出力されていれば正常です。 正しく変更が適応されていることがわかります。

また、先程と同様に、ビルドによって新たにファイルが作成されているため、全てを削除し、ビルド前の状態に戻します。

$ git clean -df

ビルド・インストール・実行が成功することを確かめたら、changelogを最終調整し、リリースしましょう。

$ gbp dch -R --commit --ignore-branch

変更前の changelog:

test (1.0.0-2+xenial) UNRELEASED; urgency=medium

  * Add world to hello.sh

 -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp>  Fri, 14 Sep 2018 13:24:52 +0900

test (1.0.0-1) unstable; urgency=medium

   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

  -- miyagaw61 <miyagaw61@unknown>  Fri, 14 Sep 2018 11:49:46 +0900

変更後の changelog:

test (1.0.0-2+xenial) xenial; urgency=medium

  * Add world to hello.sh

 -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp>  Fri, 14 Sep 2018 13:24:52 +0900

test (1.0.0-1) unstable; urgency=medium

   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

  -- miyagaw61 <miyagaw61@unknown>  Fri, 14 Sep 2018 11:49:46 +0900

タグを付けなおします。

$ gbp buildpackage --git-tag-only --git-ignore-branch --git-retag

ブランチとタグをpushします。

$ git push origin xenial
$ git push origin debian/1.0.0-2+xenial

上流の変更への追従

ここでは上流のソースコードがバージョンアップなどによって変更された場合に、 下流リポジトリのブランチで追従する方法について説明します。 上流の更新に追従する場合、まずxenialブランチを上流の適切なコミット(通常はバージョンタグ) に対してrebaseさせ、次に、patch-queue/xenialブランチをxenialブランチに対してrebaseさせるという 二段階の工程を踏みます。

準備

準備として、まずは更新された上流を用意します。

$ cd ../test
$ git checkout master
$ sed "s/hello/hi/g" -i hello.sh
$ git add hello.sh
$ git commit -m "Use hi instead of hello"
$ git push origin master
$ git tag upstream/2.0.0
$ git push origin upstream/2.0.0
$ cd ../pkg-test
$ git checkout upstream
$ git pull --rebase upstream master:upstream

タグの作成を忘れないでください。 上流に適切なタグがついていない場合は、下流でタグを付けて管理してください。

今の状況を図にすると次のようになります。

上流リポジトリのタグが付いた状態

追従

追従したい下流ブランチへ移動します。

$ git checkout xenial

上流ブランチに対してrebaseします。

$ git rebase upstream

上流ブランチに対してリベースした状態

通常であればコンフリクトは起きないはずです。xenialブランチではdebianディレクトリ以下しか編集しておらず、 上流にはdebianディレクトリが存在しないからです。 無事rebaseできたら、次のコマンドでpatch-queue/xenialブランチへの移動とxenialブランチでのrebaseを行います。 これは、quiltパッチがgitコミットとして表現されているpatch-queue/xenialブランチをxenialブランチにrebaseすることで、quiltパッチを上流の新しいバージョンに対してrebaseすることが目的です。

$ gbp pq rebase

patch-queueブランチへの移動とxenialブランチでのリベース

ここでコンフリクトが発生したら、解消します。

$ cat hello.sh
$ cat hello.sh | grep "hello, world" | sed "s/hello/hi/g" > tmp; mv tmp hello.sh # もしくはvim hello.sh
$ cat hello.sh
$ git add hello.sh
$ git rebase --continue

次に、xenialブランチとpatch-queue/xenialブランチとの差分コミットをパッチファイルという形にしてxenialブランチへ送ります。

$ gbp pq export --commit

patch-queueブランチのxenialブランチへの統合2

changelogを更新します。一気にリリースしてしまいます。

$ gbp dch -R -N 2.0.0-1+xenial --commit --ignore-branch

変更前の changelog:

test (1.0.0-2+xenial) xenial; urgency=medium

  * Add world to hello.sh

 -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp>  Fri, 14 Sep 2018 13:24:52 +0900

test (1.0.0-1) unstable; urgency=medium

   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

  -- miyagaw61 <miyagaw61@unknown>  Fri, 14 Sep 2018 11:49:46 +0900

変更後の changelog:

test (2.0.0-1+xenial) xenial; urgency=medium

   * Use hi instead of hello
   * Add debian directory
   * Add world to hello.sh
   * Update changelog for 1.0.0-2+xenial release
   * Update changelog for 1.0.0-2+xenial release
   * Rediff patches

  -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp>  Fri, 14 Sep 2018 13:43:56 +0900

test (1.0.0-2+xenial) xenial; urgency=medium

   * Add world to hello.sh

  -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp>  Fri, 14 Sep 2018 13:24:52 +0900

test (1.0.0-1) unstable; urgency=medium

   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

  -- miyagaw61 <miyagaw61@unknown>  Fri, 14 Sep 2018 11:49:46 +0900

タグを作成します。

$ gbp buildpackage --git-tag-only --git-ignore-branch

ビルド・インストール・実行ができることを確認します。

$ gbp buildpackage -us -uc --git-ignore-branch
$ sudo dpkg -i ../test_2.0.0-1+xenial_amd64.deb
$ hello.sh

"hi, world"と出力されれば正常です。 正しく変更が適応されていることがわかります。

ビルド前の状態に戻します。

$ git clean -df

git rebase upstreamによってFast-forwardで適用できなくなってしまったため、このままgit push origin xenialしてしまうとrejectされてしまいます。 なので、force pushします。

$ git push -f origin xenial

タグをpushします。

$ git push origin debian/2.0.0-1+xenial

複数人で下流リポジトリを管理している場合の注意

複数人で管理している下流リポジトリでforce pushをしてしまうと、自分以外の人がリモートに反映した情報を消してしまう可能性があります。 そのような場合には、複数人が同じ名前のブランチで作業したり、同じ名前のタグを付けることが無いように運用する必要があります。GitHub等を使っている場合はforkしてpull requestするのが王道でしょうか。 patch-queue ブランチは必ずしも共有しなくても良いと思います。

debianディレクトリ以下の変更

最後に、debianディレクトリ以下のファイルの変更方法を解説します。 debianディレクトリ以下のファイルは、patch-queueブランチで手を入れるとビルド時に怒られてしまいます。 patch-queueブランチはあくまでquiltパッチを管理するためのブランチですので、 debianディレクトリ以外のファイルを変更するときのみ使います。 debianディレクトリ以下のファイルは、xenialブランチで直接編集し、コミットしましょう。

$ git checkout xenial
$ sed "s@<insert the upstream URL, if relevant>@https://miyagaw61.github.io@g" -i debian/control

ビルドし、変更が適応されているか確認してみましょう。

$ gbp buildpackage -us -uc --git-ignore-branch --git-ignore-new
$ dpkg -I ../test_2.0.0-1+xenial_amd64.deb

Homepage: https://miyagaw61.github.ioとなっていれば変更が正しく適応されています。

ビルド前の状態に戻します。

$ git clean -df

add・commit・pushします。

$ git add debian/control
$ git commit -m "Add Homepage-Url"
$ git push origin xenial

最後に

本記事では複数のLinuxディストリビューションやそのバージョン毎に効率良くdebパッケージ開発を行う方法を紹介しました。 我々はこのノウハウを得て、これまでオリジナルのリポジトリ上で直接debianディレクトリを管理していた walb-toolswalb-tools-pkgリポジトリと分離させました。 本記事が同様のdebパッケージ管理をしたい方々のお役に立てば幸いです。

参考


  1. そのため、何の対策もせずに/usr/local以下にパッケージを直接インストールしようとすると、dh_usrlocalというツールの実行に失敗してしまいます。どうしても/usr/local以下にパッケージを直接インストールしたい場合は、こちらに書いてあるように、debian/rulesにoverride_dh_usrlocal:の一行を追記し、dh_usrlocalの実行を無効化する必要があります。

  2. PbuilderやCowbuilderなどを使用するとビルド時にdebianディレクトリ以下に新たなファイルが生成されずに済みますし、依存パッケージの記述漏れに悩むことがなくなります。

  3. gbp.confを配置可能なパスはdebianディレクトリ以外にもいくつかあります。

  4. このオプションが有効な時、--git-upstream-tagオプションは効果が無くなります。--git-upstream-tagオプションは飽くまでchangelogから抽出したアップストリームバージョンからそれに対応する上流に付けられているタグに変換するための変換方法を指定するオプションであり、--git-pristine-tarオプションが有効な時はその変換処理が必要無いからです。

  5. 今回は簡略化のため、以降のコマンド例でもpristine-tarは使用していません。

MKL-DNNで学ぶIntel CPUの最適化手法

$
0
0

初めに

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

DNN(deep neural network : 深層学習)といえばGPUや専用プロセッサを使うのが主流です。 しかしIntelはCPUで高速にDNNをするためのライブラリ MKL-DNNを提供しています。 MKL-DNNはIntelの最新CPUに対応したオープンソースソフトウェアなのでコードを見ると勉強になります。

ここではMKL-DNNで使われているテクニックをいくつか紹介します。

概要

  • MKL-DNNの紹介
  • Xbyakの紹介
  • 呼び出し規約
  • 圧縮displacement
  • ReLU
  • exp
  • 内積 vpdpbusd
  • キャッシュコントロール

想定読者

C++11とx64 CPUのアセンブリ言語の知識をある程度仮定します。 機械学習についてはその知識がなくても最適化手法が理解できるよう、最小限の説明をします。

MKL-DNNの特長

まずMKL-DNNの特長を簡単に紹介します。

  • 畳み込みや内積などの最適化された基本演算
  • RNN, LSTM, GRUなどのネットワークサポート
  • ReLU, tanh, softmaxなどの活性化関数サポート
  • 32-bit浮動小数点数と低精度int8整数をサポート
  • pooling AVG/MAX, batch normalization
  • 複数の種類を組み合わせられる柔軟なメモリレイアウト

個別の用語はさておき、いろいろな種類の行列計算を高速にするライブラリととらえれば十分です。 MKL-DNNはTensorFlow, Chainer, Caffeなどのより高次の機械学習ライブラリ内で使われる基本ライブラリです。 またIntelはMKL-DNNを組み込んでNumPyを高速化した Pythonも提供しています。

ソースファイル

MKL-DNNのIntel CPU向けに最適化されたコードは src/cpuにあります。 特にjitで始まるファイル名がAVX-512などの専用命令を使った最適化コードです。 AVX-512については AVX-512(フォーマット)詳解などをごらんください。

Xbyak

MKL-DNNのエンジン部分は拙作の Xbyakで記述されています。 XbyakはIntelのアセンブリ言語(以下asm)命令を実行時に生成するC++のライブラリです。

次の特長があります。

  1. 実行時コード生成
  2. C++でIntel形式のasmライク命令を記述可能
  3. AVX-512など最新CPUの命令に対応

1番目の特長により、CPUに応じた命令の切り替えやパラメータに応じた最適なコードを実行時に生成できます。

2番目の特長はasmの擬似命令をC++の文法で記述できるという意味です。 asm自体はIntel形式に慣れた人が極力違和感がないようなインタフェースを提供しています。 アセンブリ言語の擬似命令というのは何十年も前から殆ど変わっていない古い文法、あるいは無理やり拡張した形が多く、覚えるのも書くのも大変です。 それをC++で記述できるのはとても楽で便利です。

3番目は先日発表された 第2世代Xeonスケーラブル・プロセッサでサポートされる命令群DL Boostにも対応しています。

表記法

XbyakはIntel表記を採用しているので命令に与える引数はデスティネーション、ソースの順です。

mov(ptr[rax], rcx); // *rax = rcx; raxが指すアドレスにrcxを書き込む
add(rax, rcx); // rax += rcx;
vaddps(zmm2, zmm1, zmm0); // zmm2 = zmm1 + zmm0

また以下で出てくるコードではレジスタ名が直接使われるのではなく、適当な変数名にaliasされています。

auto dst = zmm2;
auto src1 = zmm1;
auto src2 = zmm0;
vaddps(dst, src1, src2); // dst = src1 + src2

前置きが長くなりましたが、それではMKL-DNNの中身を少しずつ見ていきましょう。

関数のプロローグとエピローグ

関数のプロローグとエピローグとは関数本体の前処理の部分、後処理の部分を指します。 レジスタやスタックを設定、復元します。

呼び出し規約

MKL-DNNはLinux, macOSだけでなくWindowsにも対応しています。LinuxやmacOSの関数の呼び出し規約とWindowsの呼び出し規約は異なります。 関数の引数に渡されるアドレスや、関数から抜けるときに値の復元が必要なレジスタが異なるのです。

引数 Windows Linux
1番目 rcx rdi
2番目 rdx rsi
3番目 r8 rdx
4番目 r9 rcx
退避不要r10, r11r8~r11
退避必要r12~r15,rdi,rsi,rbx,rbp,rspr12~r15,rbx,rbp,rsp

Xbyakもこれらの差異を吸収するStackFrameクラスを提供しています。 しかしレジスタやスタックの使い方というのは用途に強く依存するので各アプリケーションが必要に応じてラッパークラスを作ることが多いです。

MKL-DNNでは jit_generator.hppがこれらの差異を吸収しています。 abi_param1などが引数のレジスタ、abi_save_gpr_regsが退避すべきレジスタの種類を表します。

#ifdef XBYAK64constexpr Xbyak::Operand::Code abi_save_gpr_regs[] = {
    Xbyak::Operand::RBX, Xbyak::Operand::RBP, Xbyak::Operand::R12,
    Xbyak::Operand::R13, Xbyak::Operand::R14, Xbyak::Operand::R15,
#ifdef _WIN32
    Xbyak::Operand::RDI, Xbyak::Operand::RSI,
#endif
};

preamble()で関数のプロローグで保存すべきレジスタを退避、postamble()で関数から戻るときレジスタを復元します。 AVXを使うときはpostamble()でvzeroupperを実行します。

void postamble() {
  ...
  if (mayiuse(avx) && !mayiuse(avx512_mic))
    vzeroupper();
  ret();
}

vzeroupperを呼ばないとこの関数の後でSSE命令を実行したときに大きなペナルティを受けます。 コンパイラは自動的にこの命令を挿入しますが自分でasmを記述するときは忘れないようにしなければなりません。

圧縮displacement

preamble()の中で

if (mayiuse(avx512_common)) {
  mov(reg_EVEX_max_8b_offt, 2 * EVEX_max_8b_offt);
  // reg_EVEX_max_8b_offt = rbp, EVEX_max_8b_offt = 0x200
}

というコードを実行し、EVEX_compress_addr()の中でオフセットに応じて何かずらした値を返しています。

...
template<typename T>
Xbyak::Address EVEX_compress_addr(Xbyak::Reg64 base,
  T raw_offt, bool bcast = false) {
...
int scale = 0;

if (EVEX_max_8b_offt <= offt && offt < 3 * EVEX_max_8b_offt) {
  offt = offt - 2 * EVEX_max_8b_offt;
  scale = 1;
} elseif (3 * EVEX_max_8b_offt <= offt && offt < 5 * EVEX_max_8b_offt) {
  offt = offt - 4 * EVEX_max_8b_offt;
  scale = 2;
}

auto re = RegExp() + base + offt;
if (scale)
  re = re + reg_EVEX_max_8b_offt * scale;

if (bcast)
  return zword_b [re];
elsereturn zword [re];
}
...

これは何をしているのでしょうか。

x64ではメモリ操作をする命令はModR/M + SIB + displacementという形でエンコーディングします(詳細は略)。 たとえば

mov(rax, ptr[rcx + 16]);
movaps(xmm0, ptr[rcx + edx * 8 + 32]);

の16や32がdisplacementです。

displacementは符号付き8bit以内なら1byte、それ以外は4byteにエンコードされます。 同じ処理をするならbyte数が短い命令の方が命令密度が高くなります。

ところがAVX-512では一つのレジスタが512bit(=64byte)なのでデータのdisplacementもその倍数になりがちです。

vaddpd(zmm0, ptr[rax + 64 * 0]);
vaddpd(zmm1, ptr[rax + 64 * 1]);
vaddpd(zmm2, ptr[rax + 64 * 2]);
vaddpd(zmm3, ptr[rax + 64 * 3]);

そうすると従来のエンコーディングでは64 * 2や64 * 3は1byteに納まらず命令が長くなってしまいます。

そこでAVX-512では圧縮displacementというエンコーディングが導入されました。 displacementがある範囲以内では64(命令ごとに異なる)が何個分と扱うことで命令を短くするのです。 上記の例ではdisplacementに相当する部分が1byteになります。

またdisplacementは符号付きなので[0, X]の範囲よりもベースをずらして[-X/2, X/2]の方が1byteに納まる率が増えます。 このようにして全体の命令長を減らします。 たとえばrbp = 0x200のとき積和演算をする

v4fmaddps(zmm0, zmm1, ptr[rax + 64 * 32]); // 通常
v4fmaddps(zmm0, zmm1, ptr[rax + rbp * 2 + 64 * 32 - 0x400]); // オフセットずらし

は同じ処理をしますが後者の方が2byte短いです。EVEX_compress_addr()はこのようなコードを生成します。 この例に限ってはrbpをうまくとればもっと短くできますがループ内の複数の箇所で統一的に扱えるようにこの形にしているようです。

活性化関数

ニューラルネットワークでは活性化関数と呼ばれる非線形な関数が使われます。 たとえばReLUやシグモイド関数などがあります。ReLU(x)は単なるmaxなので簡単なのですがシグモイド関数は指数関数exp(x)が必要です。

ReLU

/* activation */template<typename T, typename A,
  typename U = typename utils::remove_reference<T>::type>
inline U relu_fwd(T s, A alpha) {
    return s > 0 ? s : (U)(s * alpha);
}

に対応するSIMDの実装は次のrelu_compute_vector()です(読みやすさのために加工しています)。

relu_compute_vector(const Vmm &vmm_src) {
  constint alpha_off = 0, zero_off = 1;

  // vmm_srcが入力レジスタ// vmm_aux1はテンポラリレジスタ
  vmovups(vmm_aux1, vmm_src);
  ...
  if (isa == avx2) {
    vmulps(vmm_src, vmm_src, alpha_off);             // vmm_src *= alpha_off
    vcmpgtps(vmm_mask, vmm_aux1, zero_off);          // vmm_mask = vmm_aux1 > zero_off ? -1 : 0
    vblendvps(vmm_src, vmm_src, vmm_aux1, vmm_mask); // vmm_src = vmm_mask ? vmm_src : vmm_aux1
  } elseif (isa == avx512_common) {
    vmulps(vmm_src, vmm_src, alpha_off);
    vcmpps(k_mask, vmm_aux1, zero_off, _cmp_nle_us);   // k_mask = vmm_aux1 > zero_off ? -1 : 0
    vblendmps(vmm_src | k_mask, vmm_src, vmm_aux1); // vmm_src = k_mask ? vmm_src : vmm_aux1
  }

AVX2の場合はvcmpgtpsを使ってvmm_srcがzero_offより大きい要素に対応したvmm_maskを生成します。 vblendvpsでそのマスクに対応した要素だけ移動しています。

AVX-512ではvcmppsが拡張されて要素ごとにXより大きい値に対応するマスクレジスタk_maskを生成できるようになりました。 そして新設されたvblendmpsでk_maskに従って対応する要素を移動します。

SIMD化されたexp(x)

シグモイド関数やtanh(x)に必要なexp(x)の計算方法を紹介します。 MKL-DNNでは jit_eltwise.cppのexp_compute_vectorがSIMD化されたexp(float x)のコードを生成します。 exp_compute_vectorを元に、理解しやすいように抽出したアルゴリズムを以下に記します。 SIMD化しやすいようテーブル引きを使わないアルゴリズムが使われています。

exp(x)の近似アルゴリズム

  1. exp(x) = 2x log2(e)と変形する
  2. y := x log2(e)についてy = n + a, nは整数, |a| ≦ 1/2となるようにnとaに分解する
  3. b := a log(2)とするとa = b log2(e)
  4. exp(x) = 2n + a = 2n・2b log2(e) = 2n・eb
  5. c := 2nは2の整数巾なので高速に求められる
  6. d := ebは|b| ≦ (1/2)log(2) = 0.346... なのでテイラー展開で近似計算する
  7. exp(x) = c * dを求める

exp(x)の実装

ではどのように実装しているか順に見ていきましょう。

VmmはSSE, AVX, AVX-512を切り換えるクラスです。 型に対応したSIMD命令を生成するラッパー関数が定義されています。

using Vmm = typename utils::conditional3<isa == sse42, Xbyak::Xmm,
            isa == avx2, Xbyak::Ymm, Xbyak::Zmm>::type;

...
void jit_eltwise_injector_f32<isa>::exp_compute_vector(const Vmm &vmm_src) {
  // vmm_srcが入力レジスタ
  vminps(vmm_src, vmm_src, table_val(10)); // vmm_src = min(vmm_src, table_val(10))
  vmaxps(vmm_src, vmm_src, table_val(11)); // vmm_src = max(vmm_src, table_val(11))
  vmovups(vmm_aux0, vmm_src);

入力レジスタvmm_srcを最大値table_val(10)と最小値table_val(11)にクリッピングしています。 table_valの値はJIT前に設定されています(elu_prepare_table)。

  vmulps(vmm_src, vmm_src, table_val(2)); // vmm_src *= log2(e) ; Step 2のy
  vaddps(vmm_src, vmm_src, table_val(1)); // vmm_src += 0.5

yに0.5を足してfloorをとれば四捨五入になってStep 2のnが求まります。

floorは次のようにして求めます。

if (isa == avx512_common) {
    vcvtps2dq(vmm_aux1 | T_rd_sae, vmm_src); // vmm_aux1 = int(vmm_srcをround down)
    vcvtdq2ps(vmm_aux1, vmm_aux1);           // vmm_aux1 = float(vmm_aux1)#if 1
    vcmpps(k_mask, vmm_aux1, vmm_src, _cmp_nle_us);
    vmovups(vmm_aux3 | k_mask | T_z, table_val(0)); // table_val(0) = 1

    vsubps(vmm_aux1, vmm_aux1, vmm_aux3);
#endif
  } else {
    vroundps(vmm_aux1, vmm_src, _op_floor); // vmm_aux1 = floor(vmm_src)
  }

AVXではvroundpsでfloorを計算できますがAVX-512には対応する命令がありません。 その代わりにAVX-512では整数への変換命令vcvtps2dqに丸めモードを指定できるのでマイナス無限大への切り捨て(T_rd_sae)を設定します。 結果は整数なのでvcvtdq2psでfloatに戻します。

その後に続く#if 1 ... #endifの部分は不要に見えます。試しにpull reqを出してみました。 間違ってたら後で訂正します。

少し飛ばしてStep 5の2nの処理を見ましょう。

  vcvtps2dq(vmm_aux1, vmm_src);             // vmm_aux1 = int(vmm_src)
  vpaddd(vmm_aux1, vmm_aux1, table_val(4)); // vmm_aux1 += 0x7f
  vpslld(vmm_aux1, vmm_aux1, 23);           // vmm_aux1 <<= 23

floatのバイナリ表現をX = [s:a:b](s:1bit, a:8bit, b:23bit)とするとXが表すfloatの値は(-1)s2a-127(1+b/223)です。

したがって32bit整数として指数nに0x7f(=127)を足して左に23bitシフトするとfloatとしての2nができあがります。 簡潔でうまい方法ですね。

Step 6はテイラー展開の処理です。ここでは5次までの項を計算しています。p[i] = 1 / i!として

exp(x) = 1 + x p[1] + x2 p[2] + x3 p[3] + x4 p[4] + x5 p[5]

= 1 + x(p[1] + x(p[2] + x(p[3] + x(p[4] + x p[5]))))

なので5回の積和演算命令vfmadd213psで処理します。

// y = y * x + p4
vfmadd213ps(vmm_src, vmm_aux0, table_val(8));
// y = y * x + p3
vfmadd213ps(vmm_src, vmm_aux0, table_val(7));
// y = y * x + p2
vfmadd213ps(vmm_src, vmm_aux0, table_val(6));
// y = y * x + p1
vfmadd213ps(vmm_src, vmm_aux0, table_val(0));
// y = y * x + p0
vfmadd213ps(vmm_src, vmm_aux0, table_val(5));  //exp(q)

最後にStep 5とStep 6の値を掛けて完了です。

// y = y * 2^n
  vmulps(vmm_src, vmm_src, vmm_aux1);

exp(x)のような複雑な関数を20命令以下で計算できるとはすごいですね(しかも16個同時に)。

内積

まずベクトルの内積について紹介しましょう。 DNNでは単精度浮動小数点数(float)を使っていましたが、近年精度を落としてもよいところは1バイト整数や1ビットで行うアルゴリズムが提案されています。

Intelの第二世代Xeon SP(スケーラブルプロセッサ Cascade Lake-AP)で搭載されたDL Boost(Deep Learning)命令を使うと1バイト整数ベクトルの内積を高速に求められます。

vpdpbusd命令

CPUがAVX512_VNNI命令セットに対応しているとvpdpbusdを使えます。 vpdpbusdはuint8_t u[64]とint8_t s[64]の各要素をそれぞれ符号無し整数、符号あり整数としてintに拡張し、4次元ベクトルの内積を16個並列に求めてもとのint dst[16]に足し合わせます。

この処理をCで記述すると次のようになります。

// vpdpbusd(dst, u, s)void vpdpbusd(int *dst, constuint8_t *u, constint8_t *s)
{
  for (int i = 0; i < 16; i++) {
    int sum = dst[i];
    for (int j = 0; j < 4; j++) {
      sum += s[i * 4 + j] * u[i * 4 + j];
    }
    dst[i] = sum;
  }
}

従来のAVX-512では同じことを実現するにはvpmaddubsw, vpmaddwd, vpadddの3命令が必要でした。 jit_avx512_core_gemm_s8u8s32_kern.cppではどちらのCPUにも対応できるよう次のようにしています。

// Use vpdpbusd if VNNI available, otherwise emulate.void jit_avx512_core_gemm_s8u8s32_kern::dot_product(const Xmm &dst,
  const Xmm &src1, const Xmm &src2)
{
  if (vnni)
    vpdpbusd(dst, src1, src2);
  else {
    vpmaddubsw(dp_scratch, src1, src2); // [a0 b0 + a1 b1:a2 b2 + a3 b3:...]
    vpmaddwd(dp_scratch, ones, dp_scratch); // [a0 b0 + a1 b1 + a2 b2 + a3 b3:...]
    vpaddd(dst, dst, dp_scratch);
  }
}

これにより3倍の性能が得られるそうです(Intel AVX512-Deep Learning Boost: Intrinsic Functions)。 こんな計算が1命令、1クロックサイクルでできるんですね……

畳み込み

2個の同じ大きさ行列A, BがあったときAとBの畳み込みA*Bとは、行と列の同じ位置にある要素同士を掛けて全て足したものです(行列を連続する1次元ベクトルとみなしたときの内積)。

画像認識などでは大きな行列Aと小さな行列Bに対して、Aの左上からBと同じ大きさの部分行列A'を取り出して畳み込みA'*Bを求め、 次にそのとなりの部分行列とBの畳み込み, ...と値を並べて新しい行列を作る操作がよく行われます。

f:id:cybozuinsideout:20190411223730p:plain
convolusion
図は MEC: Memory-efficient Convolution for Deep Neural Network Figure 1の引用。

薄いグレーの3x3とBとの畳み込みが[[0, 0, 0], [2, 1, 1], [0, 1, 1]] * B = 3。 点線で囲まれた3x3とBとの畳み込みが[[1, 1, 0], [1, 2, 0], [1, 1, 1]] * B = 4です。

素直な実装はそれほど難しくないのですが、メモリアクセスが飛び飛びになりSIMD化しづらいし遅いです。 そのため行列Aをシーケンシャルアクセス可能なより大きな行列に置き換え、BLASなどの行列演算ライブラリで処理する方法(im2colなど)がよく使われます。

行列の積の高速化についてはw_oさんによる最内ループからはじめる深層学習 (waifu2xの高速化)いまどきのmatmulがとても勉強になります。

MKL-DNNではカスタム化されたsgemmを実装しています。

キャッシュコントロール

大容量だけど遅いDRAMの一時的な代用領域としてキャッシュメモリが使われます。 CPUに近い側から順にL1, L2, L3というキャッシュメモリがあります。 MKL-DNNではキャッシュサイズなどに応じて細かな制御をします。

prefetchとは指定したアドレスのメモリをキャッシュに読み込ませる命令です。 たとえば、今ある計算をしてからXのメモリを読むと分かっているとします。 そのときprefetch(X)を先に発行しておくと、計算している間にXの値がキャッシュに読み込まれて、実際にmovを実行したとき高速に動作します。 どれぐらい前にprefetchしておくべきかはキャッシュのサイズや速度によって変わります。

たとえばWinogradというアルゴリズムの実装 jit_avx512_common_conv_winograd_kernel_f32.cppでは、キャッシュサイズに応じてprefetch命令の挿入すべき間隔を求めています。

/* assumption: when fetch in Li, data is already in L(i+1) */int cache_latency;
switch (cache_type_) {
case L1: cache_latency = 14; break;
case L2:
case L3:
default: cache_latency = 250; break;
}

prefetch_distance_ = div_up(cache_latency, nb_cache_lines_to_prefetch_);

このL1, L2のレイテンシはXeon-SPの値なんですかね。

そしてこのprefetch_distance_やキャッシュの種類に応じて適切なprefetch命令を挿入するprefetch(int instruction_number)を次のように実装しています。

void prefetch_inst_(const Xbyak::Address &addr)
{
  switch (cache_type_) {
  case L1: cg_->prefetcht0(addr); break;
  case L2: cg_->prefetcht1(addr); break;
  case L3: cg_->prefetcht2(addr); break;
  default:
  break; // TODO: raise an exception or put an assert
  }
}

void prefetch(int instruction_number)
{
  if (instruction_number % prefetch_spread_ == 0) {
  for (int i = 0; (i < prefetch_blk_)
    && (prefetches_issued_ < nb_cache_lines_to_prefetch_);
    i++, prefetches_issued_++) {
    prefetch_inst_(cg_->EVEX_compress_addr(
      reg_base_addr_, (cache_block_size_ * prefetch_distance_)
          * sizeof(data_t)
        + (prefetches_issued_ * 64)));
  }
  }
}

データの書き込みの際にもキャッシュの状態は変わります。通常書き込んだ値は近いうちに読み込まれると想定されるためキャッシュに入れておくと効率がよいからです。 しかし巨大な行列の計算では書き込んだ値が次読まれるまでにたくさんのデータが読み込まれて、せっかくキャッシュに入った値が使われる前に破棄されることがあります。 それならキャッシュに入れない方がよいです。

事前にそうなることが分かっている場合には通常のvmovupsの代わりにキャッシュを変更しないvmovntpsを使うとよいです。 次のstore_outputという関数ではデータのサイズがL3キャッシュサイズ(LLC_data_size)を超える場合はvmovntpsを使うようにしています。

auto store_output = [=](bool output_is_aligned) {
  for (int tile = 0; tile < jcp.dimN_reg_block; tile++) {
    Zmm zmm(jcp.zmm_start + tile);
    if (output_is_aligned
      && jcp.dimK_nb_block == 1
      && (jcp.dimN * jcp.dimM * alpha * alpha
        * sizeof(float) > 2 * LLC_data_size))
      vmovntps(zword[reg_dstC + 64 * tile], zmm);
    else
      vmovups(zword[reg_dstC + 64 * tile], zmm);
  }
};

データ処理の方法

一般的にDNNの学習フェーズでは畳み込み、活性化関数、batch normalizationといった計算を順に行います。 その際それぞれの処理でデータの並び替え(reorder)をします。 ここのメモリアクセスが多く、かつランダムアクセスになりがちなのでMKL-DNNでは畳み込みやそれに続く活性化関数の処理を統合しつつメモリアクセスを減らします。

flow
flow
INTEL MATH KERNEL LIBRARY FOR DEEP NEURAL NETWORKS p.10より引用

おわりに

以上、駆け足でしたが気になったテクニックをいくつか紹介しました。

並列プログラミングのためのOpenMPやTBBに、C++のtemplateやラムダ式、そしてむき出しのAVX-512が混在するソースファイルは眺めてるとトリップしそうです。

よくこんな複雑なものを作れるなと感心します。みなさんもぜひごらんになってみてください。 このテキストが少しでも手助けになれば幸いです。

脆弱性報奨金制度2019 はじまります

$
0
0

こんにちは!Cy-PSIRTの大塚 純平です。

サイボウズ脆弱性報奨金制度(以下、報奨金制度という。)も今年で6年目になりました。 本エントリでは、2019年度の報奨金制度についてご紹介いたします。

脆弱性報奨金制度2019

期間

2019年4月20日(土) ~ 2019年12月18日(水)

※報奨金制度2019のルールが適用されるのは2018年12月20日(木)17時 ~ 2019年12月18日(水)の間に着信したご報告になります。

※2019年12月19日(木)以降もご報告を受け付けますが、適用されるルールは次年度の報奨金制度のルールです。

認定基準

脆弱性の認定可否の基準は脆弱性報奨金制度ルールブックおよび、脆弱性認定ガイドラインをご覧ください。

※状況に応じて脆弱性認定ガイドラインの追加または変更を随時行っています。

施策

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

報奨金制度2019でも2018年同様、通年で実施いたします

製品ごとに報奨金の倍率が異なります。
kintoneやcybozu.com 共通管理、cybozu.com 運用基盤では5倍、Garoonでは2倍を掛けた報奨金をお支払いいたします。 脆弱性報告数および脆弱性検出数が減少傾向にある製品に、注力していただけるように倍率を変更しました。

各プロダクトと倍率の変更について記載している表
製品による係数

バグハン合宿2019

2019年4月20日(土), 21日(日) に、2年ぶり3度目となる「バグハン合宿 2019」を開催いたします!

バグハン合宿ロゴ
バグハン合宿ロゴ
合宿はチーム戦(1チームにハンター3人+弊社開発1人)で行われます。合宿でのみ触れる製品を用意し、ハンター同士、ハンターと運営との交流にも力を入れる予定です。報奨金が支払われると共に、優勝チームなどには賞品を用意しております。
こちらも追って報告いたしますので、お楽しみにお待ちください!

ポイント制度

ご報告いただいたもので、認定とならなかったものに対しても”ポイント”を付与し、累計獲得ポイントに応じて感謝の気持ちとしてオリジナルTシャツなどの賞品を贈呈させていただきます。

2018年度以前の報奨金制度では、脆弱性認定しなかった報告でも、有益な情報を多数報告いただきました。
2019年度の報奨金制度ではそのような報告に対して、報奨金のお支払い対象とはならなくても、ポイントという形で感謝の気持ちをお伝えできればと考えています。

弊社が決めた基準に基づいて付与される通常ポイントだけではなく、年に一度、影響度の大きさなど 総合的に判断して付与する特別ポイントなど、様々な加点項目をご用意しております。

詳しい加点ルールにつきましては、脆弱性報奨金制度ルールブックをご確認ください。

賞品の送付時期

1. 累計獲得ポイントが100ptに達したときに賞品を贈呈

累計獲得ポイントを集計し、100ptに達した翌月に弊社より賞品を送付させていただきます。
※ポイントは年度切り替え時にリセットされます。

2. 年間累計獲得ポイントランキングで上位3位にランクインしたら賞品を贈呈

報奨金制度2019終了後、累計獲得ポイントランキングの上位3名の方には、賞品を送付させていただきます。

※ 1, 2は異なる賞品をご用意する予定です。

リアルタイムランキングへの掲載

今年度は、次の2つのリアルタイムランキングを、Cybozu 報奨金制度公式アカウントにて掲載いたします。

  • 報奨金額の上位5名(お名前のみ ※報奨金額は掲載しません)
    ※順位に変動があった際に掲載いたします。

  • 累計獲得ポイントの上位5名(お名前と累計獲得ポイント)
    ※週次で掲載いたします。

最後に

2018年度の報奨金制度では、多くのハンターの皆様に支えられ、過去最多のご報告をいただきました。実際の報告件数等につきましては、 別エントリで報告させていただきます。

サイボウズ製品のセキュリティ品質向上にご協力いただき、ありがとうございました。 2019年度の報奨金制度でも、皆様のご報告をお待ちしております。


YakumoのAWS開発環境とデプロイパイプラインの紹介

$
0
0

こんにちは、Yakumoチームの@ueokandeです。 本日はYakumoプロジェクトで構築したAWS開発環境とデプロイパイプラインについて紹介します。

What is Yakumo?

背景

Yakumoプロジェクトは2018年1月にスタートした、米国市場向けのkintone.com開発プロジェクトです。 現在米国市場向けにリリースしているkintoneは日本のcybozu.comと同じ基盤で提供しています。 そのため海外からレイテンシが大きいという問題や、販売管理システムを共通して使っているため米国市場向けの施策が打ちにくいといった課題があります。 そこで米国を含むグローバル市場向けにkintoneの価値を早く提供できるよう、今の日本のインフラからkintone.comを切り出して別のインフラで提供することにしました。 Yakumoプロジェクトは現在のkintone.comのAWS移行プロジェクトになります。

更に詳しい背景情報は以下の資料をどうぞ。

Yakumoプロジェクトの技術的な挑戦

Yakumoプロジェクトはビジネス面だけでなく、技術面や開発体制も新たなチャレンジが多いです。 今のcybozu.comではデプロイ作業がSREチーム任せになってしまっているため、SREチームとアプリケーション開発チームとの間でノウハウが分断してるという課題があります。 また長年蓄積されたレガシーシステムが多く、新しいリリースフローの導入も容易ではありません。 そのためYakumoでは他の製品に先駆けて、新たなDevOpsQAな開発フローにチャレンジしています。 国内のインフラに関しては、刷新プロジェクトNecoが別に進んでいます。

Yakumoプロジェクトはまず、チーム編成を技術レイヤーで分断しない構成にしました。 そのためにチーム内で開発・テスト・運用までのフローが完結できる体制づくりにチャレンジしています。 単体テストから受入試験まで自動で実施して、その後のデプロイもas codeとして形にしています。 元kintone開発者やSREを含めて全ての開発者が各フローに携われるようになってます。

この記事では、Yakumoの開発環境と構築したデプロイパイプラインについて紹介します。

Yakumoの開発環境

YakumoプロジェクトではkintoneをAWS上に構築します。 そのために販売管理システムを米国向けに作り直し、非同期ジョブやサービスディスカバリなどのミドルウェア群もAWS上で利用できるよう移植しました。 またMySQLやオブジェクトストレージにAWSのマネージドサービスを利用しています。 kintone本体やkintoneが利用する各ミドルウェアは、Amazon Elastic Container Service for Kubernetes (Amazon EKS) を使ってKubernetesクラスタにデプロイします。

Yakumoチームでは複数のサブチームに分かれて開発を進めています。 開発速度を上げるために、それぞれのサブチームごとに独立した環境を用意しています。 この独立した環境をYakumoではPlaneと呼んでいます。 たとえばIgaチームは用意されたIga Plane上で開発を進めます(Yakumoのサブチームは忍者の隠れ里から命名されてます)。 masterブランチにマージされた成果物のためにMaster Planeを用意してあり、Master Planeではドッグフーディング用のkintoneがデプロイされています。

各Planeは1つのAWSアカウント内に同居して、それぞれのPlane間はネットワーク的に分離されています。 仮にPlane上の環境を壊したとしても、他の開発者に影響が無いので安心して開発を進められます。

Yakumoの開発環境

デプロイパイプライン

PlaneへのデプロイはCircleCIによって自動化されており、pushされたらデプロイが自動で開始します。 そのため人の手によるデプロイ作業はほぼ無く、GitHub上にマージしてしばらく待てば、利用可能な環境が用意されます。

YakumoのCloudFormationや全てのミドルウェアはモノレポで管理しています。 各Planeのブランチへのpushがデプロイのトリガーになっています。 たとえばIga Planeにデプロイするには、igaブランチに変更をマージしてpushします。 開発はトピックブランチを作成して、それぞれのPlane環境で動作が確認できればマージというフローにしています。

現在のデプロイパイプラインは以下のようになっています。 それぞれのステップについて順を追って説明します。

YakumoのCircleCI Workflow

各ミドルウェアのビルド

この段階ではミドルウェアごとにビルドジョブを分離して、それぞれ並列に実施します。 各ミドルウェアで単体テストが通過すれば、成果物をDockerレジストリやS3にアップロードします。

ビルドでできあがった成果物は、ソースコードのハッシュ値を付けてアップロードします。 例えばDockerイメージはイメージタグにハッシュ値を、Lambdaの場合はS3のキー名にハッシュ値を利用します。 各ミドルウェアにバージョンという概念が存在せず、常にpushした最新の成果物がAWS上へデプロイ可能になります。

CloudFormationの適用

次のステップはCloudFormationの適用です。 CloudFormationはスタック単位でPlaneごとに分離して、別々のRDSやEKSクラスタを構築します。 Plane間で共有してるリソース(IAMや外部サービスのトークン)以外は、互いのPlaneには変更の影響しません。 またネットワーク的にも分離されています。 そのため開発時のCloudFormationの実験や更新も、他の開発者に迷惑をかける事なく実施できます。

CloudFormationは適用時間短縮のために、一定の単位でファイルを分割して、複数のCloudFormationを並列して実行します。 それぞれのCloudFormation間で依存関係があるので、YAMLをパースして依存関係にそった順序で適用するツールを作りました。

Kubernetes上にデプロイ

この時点で各PlaneごとのAWSリソースがデプロイされ、Kubernetesが利用可能な状態になりました。 このステップではkintoneの動作に必要な各ミドルウェアをAmazon EKS上にデプロイします。

YakumoではKubernetesマニフェストをGoのテンプレートを埋め込んだYAMLで記述して、独自のツールで生成してます。 HelmやKubernetss 1.14から標準で用意されてるKustomizeといった、Kubernetesマニフェストをカスタマイズする仕組みはいくつかあります。 しかしYakumoでは以下の理由から、テンプレートを元にマニフェストを生成するツールを作りました。

  • AWS上で生成されるID (ARN) を参照する必要がある
  • Plane名やアーカイブのハッシュ値を埋め込む必要がある

HelmやKustomizeを使っても別途ツールが必要になりそうなので、現段階ではHelmやKustomizeなどの導入はまだしてません。

テスト

ここまでのジョブで、kintoneがAWS上にデプロイされて利用可能な状態になりました。 最後のステップとして、構築した環境が正常に動作するかを試験します。 このステップでは自動化したE2Eテストを実際に構築した環境に対して流し、単体テストでは検知できない不具合やリグレッションを検知します。 E2EテストはKubernetes上にJUnitをコンテナ化したものと、Selenium HubおよびSelenium Nodeをデプロイすることで実施しています。

すべてのテストを流すととても時間がかかるので、一部の重要なテストケースを抜き出したスモークテストのみを実施しています。 すべてのテストケースはDailyで実行されます。

全てのスモークテストが通って不具合が無いとわかれば、開発者はmasterブランチに変更をマージします。

まとめ

この記事ではYakumoの開発環境とDevOpsQAについて紹介しました。 プロジェクトがスタートして1年ちょっとですが、開発チームが湯水のようにCircleCIを利用しており、これまでに300,000を超えるCircleCIジョブが実行されました。

まだまだ新しいことばかりで試行錯誤な部分もありますが、Yakumoの成果物やノウハウは国内の製品開発にも生かされる予定です。 国内外合わせて、ユーザーに最大の価値を提供できるようなプロダクト開発を目指していきます!

サマーインターンシップ2019を開催します!

$
0
0

こんにちは!インターンシップ運営チームの大塚 純平です。

毎年ご好評いただいているエンジニア&デザイナーの学生向けサマーインターンシップを、この夏も開催いたします。

サイボウズサマーインターンのバナー画像

サイボウズではWebサービスやモバイルアプリの開発、UIデザイン、品質保証といった製品開発プロジェクトだけではなく、サービスの運用とそれに必要なミドルウェア群の開発も自社のエンジニアがやっています。 そんなサイボウズだから、幅広いレイヤーを体験できるコースをご用意しました。

各コースのコンテンツ以外にも、社内の勉強会や、社長または開発本部長とのランチ会に参加したり、気になるチームの社員を指名して、1対1で話したりできる時間もご用意しています。 メンターの手厚いサポートを受けながら実業務を体験してみませんか?

インターンシップ情報

過去のインターンシップ開催レポートなどのインターンシップに関連する記事も御覧ください。

blog.cybozu.io

募集要項

コース

日程

  • 第1日程 8月5日(月)~9日(金)
  • 第2日程 8月26日(月)~30日(金)
  • 第3日程 9月9日(月)~13日(金)

場所

サイボウズ株式会社 東京オフィス

※Webサービス開発コースについては、東京オフィスに加え、大阪オフィス松山オフィス広島オフィスでも開催いたします。

就業時間

9:00~18:00

※ただし最終日は懇親会を実施するため20時~21時頃解散となります。

対象

2021年4月の入社が可能な学生

募集人数

各コース1日程につき3~8名程度

選考

書類選考、面接(Webも可)1回

待遇

5日間 10万円

遠方からご参加いただく方には、交通費・宿泊費を支給します。

その他、社長または開発本部長とのランチ会や、エンジニアとの懇親会を開催します。

また、インターンシップに参加いただいた方は全員、本選考を優遇いたします。

エントリー方法

こちらよりエントリーしてください。

第一締切:6月9日(日) 23:59 ※優先的に希望日程を考慮します。

第二締切:6月30日(日) 23:59

お問い合わせ

  • 人事本部 新卒採用チーム
  • Tel: 03-4306-0870
  • E-mail: recruit_contact@cybozu.co.jp

コース紹介

Webサービス開発コース

ウェブサービス開発コース

概要

サイボウズが提供するkintoneの新機能のプロトタイプを開発します。

実際のユーザーや社内からの要望を元に、仕様検討や実装、テストの自動化にチャレンジしてもらいます。

インターン生同士でチームを組んで、kintoneチームが実践しているプロセスに沿って開発をしてもらいます。

インターン期間中はkintoneチームのエンジニアがメンターとして指導やコードレビューを担当します。大規模なWebサービス開発の現場を体験することができます。

必要な経験/スキル

  • Webサービス開発の経験
  • JavaもしくはJavaScriptの知識

あると望ましい経験/スキル

  • Git/GitHubの使用経験

2018年開催レポート

blog.cybozu.io

モバイルアプリ開発コース

モバイルアプリ開発コース

概要

サイボウズで開発しているモバイルアプリの機能改善をしてもらいます。

インターン用に用意した課題ではなく、実際の製品のソースコードに触れ、スクラム開発やモブプログラミングなど、開発チームが行っている進め方に則ってインターンを行うので計画から実装、レビューまでの一通りの流れを体験することができます。

必要な経験/スキル

  • iOS/Androidのネイティブアプリ開発経験(どちらか一方でOK)
  • XcodeおよびAndroidStudioの使用経験
    • 使用言語
      • iOS: Swift
      • Android: Kotlin

あると望ましい経験/スキル

  • RxJava, RxSwiftなどのリアクティブプログラミングの知識(概要をなんとなく知っている程度で大丈夫です)

2018年開催レポート

blog.cybozu.io

UX/UIデザイン&リサーチコース

UX/UIデザイン&amp;リサーチコース

概要

サイボウズのデザイングループメンバーとともに、製品・サービスのデザイン業務に取り組んでいただきます。自由な発想を活かして新しいデザインを提案してください。

必要な経験/スキル

  • PCやスマホ向けのデザイン経験
  • ポートフォリオ

あると望ましい経験/スキル

  • ユーザーリサーチの興味、経験
  • プロトタイピングスキル
  • グローバルへの興味
  • チャレンジ精神

2018年開催レポート

blog.cybozu.io

品質保証・セキュリティコース

品質保証・セキュリティコース

概要

サイボウズ製品の品質保証活動がどのように行われているか、品質がどのようなプロセスで確保されているのかを、実際の業務を体験しながら学んでいただきます。

サイボウズでは、セキュリティも製品の品質であると考えているため、「セキュリティテスト」も品質保証のコースに含めています。

実施内容としては、以下を予定しています。

  • サイボウズの品質保証活動について講義
  • サイボウズ製品のテスト設計、テスト実施、バグ報告を通し、品質保証の実業務を体験
  • サイボウズのセキュリィ対策と体制について講義
  • サイボウズ製品のセキュリティテストを体験

必要な経験/スキル

  • ITに関する基本的な知識がある
  • Webサービスのテストに興味がある、または、Webサービスの脆弱性の概要を説明できる(例:SQLインジェクション、XSSの概要を説明できる)
  • 「品質保証」という概念を説明できる

あると望ましい経験/スキル

  • Webサービスのテスト経験
  • Webサービスのセキュリティテスト経験
  • Webサービスの脆弱性の攻撃方法を知っている(例:SQLインジェクション、XSSを使って攻撃する方法を知っている)

2018年開催レポート

blog.cybozu.io

クラウド基盤コース

クラウド基盤コース

概要

cybozu.com のクラウド基盤改善につながる技術について調査・検証して評価する。

例:

  • マルチテナント構成での並列 I/O の処理方式の改善(RAID 構成最適化や NVMe SSD 活用)
  • サーバリソース使用状況のプログラムによる数理・統計的な分析(機械学習まで含めても良い)
  • コンテナ環境や Systemd サービスのリソース制御方式の検討
  • 高可用性や耐障害性を向上させる方法の検討
  • MySQL などのミドルの性能最適化チューニング

※クラウド基盤に関連する技術領域であれば上記に例示されていないテーマも可

必要な経験/スキル

  • Linux 環境で実験するために必要な基礎スキル
  • 何か一つ選択するテーマに関して詳しいこと

あると望ましい経験/スキル

  • コンピュータ科学、情報科学に関する基礎教養
  • Python や Go での開発経験

2018年開催レポート

blog.cybozu.io

全国各地からのエントリーをお待ちしています!

Cephalocon Barcelona 2019 現地レポート 1日目

$
0
0

こんにちは、Necoプロジェクトのsatです。Necoではストレージ基盤として分散ストレージソフトウェアCephを使う予定です。その情報収集のために現在わたしはCephalocon 2019 BarcelonaというCephについての大きなカンファレンスに参加しています。Cephaloconは今日と明日の2日間にわたって開催されるため、2日に分けて本イベントの様子を報告いたします。

Cephaloconは2日後に始まるKubeCon + CloudNativeCon Europe 2019と同じ場所で開催されています。KubeConについても毎日レポートする予定です。

Cephaloconの参加者は開発者やユーザなどを合わせて合計数百人程度です。それぞれOSディストリビュータ、クラウドベンダ、およびストレージベンダなどさまざまなバックグラウンドの人たちが集まっています。

f:id:cybozuinsideout:20190520045451j:plain
会場の様子

Cephについてまったくご存じないかたは、以前このブログにおいて簡単に紹介した記事を事前にいただくと本記事の内容を理解しやすいかと思います。

blog.cybozu.io

では、ここからは本日わたしが参加したセッションのうち、印象に残ったものを紹介いたします。

Keynote: State of the Cephalopod - Sage Weil, Co-Creator, Chief Architect & Ceph Project Leader, Red Hat

sched.co

オープニングセッションはCephのオリジナル開発者であるSage Weil氏によるキーノートでした。Cephとは何かから始まって、その後に、ここ数年の間に出たバージョンに取り込まれた様々な機能を紹介しました。

ここでCephのバージョンについて説明しておきます。現在Cephは9か月ごとに新バージョンが出るようになっています。各バージョンにはコードネームがついています。名前はアルファベット順になっていて、それぞれ頭足類(イカやタコ)にちなんだ名前がついています。なぜ頭足類かというと、CephがCephalopod(英語で頭足類の意)にちなんで名づけられたものだからです。ここ数回のバージョンにはLuminous,Mimic,Nautilus(現在の最新版)という名前がついています。

f:id:cybozuinsideout:20190520124706j:plain
Cephのリリーススケジュール

ダッシュボードはWebベースでCephの管理をするツールです。以前からCeph本体とは独立した形で存在していましたが、Luminousから公式のダッシュボードが導入され、それがさらにMimicにおいて新たに作り直されて今の形になりました。ダッシュボードについての詳細はこちらをごらんください。

Cephは複雑なソフトウェアなので、そのまま使うには管理コストが高いです。このため、Cephの管理を楽にするためのさまざまなオーケストレーターが存在します。ここではceph-ansible, Ceph本体に取り込まれているdeapseaやシンプルなssh orchestrator、およびKubernetes上で動作するRookなどを紹介していました。

Cephは設定項目が大量にあるため、それぞれを適切に設定するのにユーザは頭を悩ませてきました。その最たるものがPlacement Group(PG)というものの数です。Cephの各RADOSオブジェクトがどのOSDに配置されるかは、オブジェクトが属するPGによって決まります。このPGの適切な数を決めるのは次のような理由によってユーザの大きな負担になっていました。

  • 適切な値を管理者が自分で見積もらなくてはいけない
  • 見積もりが難しい
  • 不適切な値を設定すると性能が劣化したり、OSDごとに配置されるデータ量の不均衡が発生したりといった問題が起きる

この問題を解決するためにNautilusにおいてはPGの数をCephが自動的に決めてくれるPGオートチューニング機能が追加されました。

今後もCephにはさまざまな機能が追加されたり、性能が向上したり、設定が容易になったりしていくことでしょう。

Day 2 Operations : Make Friends with Your Ceph Cluster - Adrien Gillard, Pictime Groupe

sched.co

このセッションは長年Cephクラスタを運用してきた発表者が自身のノウハウを共有してくれるというものでした。各種デバイスやdaemonが正常に動作しているかを確認しておくべきということ、および、それには具体的にどのようなコマンドやツールを使えばいいかといったことが具体的に述べられました。ログレベルを変える方法やそれを一か所に集約する方法などについても述べていました。

バージョンアップについての話題もありました。クラスタを特定バージョンに固定して塩漬けにするのではなく、最新バージョンほど機能が高く性能もよくなり、使い勝手もよくなっているため、継続的にバージョンを上げることがよいというメッセージを発信していました。その際はリリースノートを注意深く読んで、書いてある手順通りにアップグレードをすることを口を酸っぱくして言っていました。発表者の苦労が偲ばれます。

このセッションにおいては「得た知見をみんなに共有しよう」と言っていたのがとくに印象的でした。まさに有言実行なので素晴らしいですね。

Object Bucket Provisioning in Rook-Ceph - Jonathan Cope & Jeff Vance, Red Hat

sched.co

本セッションはKubernetesにオブジェクトストレージを管理する機構を追加するという話でした。Kubernetesはファイルシステムやブロックデバイスをプロビジョニングしてpodから使えるようにする方法をPersistent Volume(PV), Persistent Volume Claim(PVC)というしくみを使って実現しています。しかし、オブジェクトストレージについてはこのようなしくみが存在しないため、管理が面倒です。

発表者のアプローチは、Kubernetesのカスタムコントローラやカスタムリソースを作ることによってオブジェクトストレージのバケットをプロビジョニングするというものでした。カスタムリソースはPV,PVCに倣ってObject Bucket(OB), Object Bucket Claim(OBC)という名前になっています。

発表のタイトルどおり、OBやOBCはもともとRookで使うために出てきたアイデアなのですが、Rook以外にも使えそうなのでいずれはKubernetes公式機能として取り込みたいということでした。

Making Ceph Fast in the Face of Failure - Neha Ojha, Red Hat

sched.co

本セッションはCephクラスタ上のストレージデバイスが故障した状態からのリカバリ処理についてのものでした。デバイス故障からの復旧は直観的には速ければ速いにこしたことはなさそうなのですが、事はそう単純ではありません。

ストレージデバイスが故障したとき、当該デバイスに存在するRADOSオブジェクトの冗長度を回復するためには、当該データを他のOSDにコピーしなければなりません。このリカバリ処理においてはI/Oが発生するため、リカバリ処理が動いている間はクライアントI/Oの性能が低下します。ここでリカバリ処理を高速に終わらせようとすると、この処理のためのI/O負荷が高くなり、それだけクライアントI/Oの性能劣化が激しくなってしまいます。

発表者はリカバリ処理におけるクライアントI/Oの性能劣化が最新から数えて6つ前のバージョンであるHammerから最新版であるNautilusまでにどのように改善してきたかについて、性能測定結果だけでなく、技術的になぜそうなったかについて述べました。

Hammerにおいてはリカバリ処理に対する制限はほぼ何もつけられなかったのですが、Luminousにおいて、一定量リカバリ処理が走ってからはしばらくしてからでないと次のリカバリ処理が動かないというスロットリング機能が入りました。

Mimicではリカバリ処理が非同期処理になりました。どういうことかというと、一つ前のバージョンのLuminousまでは、あるRADOSオブジェクトのリカバリ処理が動作している途中に当該オブジェクトへの書き込み処理が発生した場合、この処理はリカバリ完了まで待たされていました。それに対して非同期リカバリの場合はこのような待ちが無く書き込みができるようになりました。この機能はNautilusになるとさらに洗練されました。

今後もこのような改善は続く見込みです。

Cephalocon Barcelona 2019 現地レポート 2日目

$
0
0

こんにちは、Necoチームのsatです。昨日に続きCephalocon Barcelona 2019のレポートをいたします。

f:id:cybozuinsideout:20190521123804j:plain
キーノートの様子

一日目については以下記事をごらんください。

blog.cybozu.io

Keynote: What's Planned for Ceph Octopus - Sage Weil, Co-Creator, Chief Architect & Ceph Project Leader, Red Hat

sched.co

昨日に引き続きCephの生みの親であるSage Weil氏によるキーノートです。昨日はCephのこれまでと現状についてが中心でしたが、ここでは将来Cephがどのように変化していくかについて述べられました。

ユーザビリティの面ではRookやsshによるオーケストレータに力を入れること、すべての操作をCLI経由でできるようにすること、ダッシュボードで操作できることを増やすこと、ドキュメントを充実させること、アップグレードを容易にすることなどが挙げられました。ドキュメントについては数か月前に発足したCeph Foundationを通じて探しているようです。これまでCeph Foundationは活動実態が謎だったのですが、初めてどういう活動しているかの一端が見えました。

品質面では主にデータ収集、とくにクラッシュ時のレポートの充実、および、これらの機能を使うようユーザに推奨することに力を入れるとのことです。これによってユーザからも、およびユーザからトラブル報告を受けた開発者たちがトラブルシュートするのが楽になることでしょう。

機能面ではCephクライアントから見たサービスレベルを制御するRADOS QOSや、高速なデバイスの性能を引き出すためのCrimsonというストレージバックエンドを取り込む予定だということを述べました。

CRUSH-ing the OSD Variance Problem - Tom Byrne, Storage Sysadmin

sched.co

このセッションの発表者はRutherford Appleton Laboratory (RAL)が使っているechoと呼ばれるCephクラスタの管理者です。その彼がechoの運用を通じて得たOSD間のデータの分散について知見を共有してくれました。echoは総容量40PB、現在の使用データ量が20PBという巨大なクラスタです。

セッションの前半ではCephクラスタの見かけ上の空き容量と実質的な空き容量についての説明をしてくれました。CephはどのRADOSオブジェクトをどのOSDに配置するかをCRUSHというアルゴリズムによって決めています。これによってデータはすべてのOSDにおおよそ均等に配置される…かというと話はそう単純ではありません。

各OSDに配置されるデータ量はおおよそ正規分布しています。その平均値はおおよそ以下のように理想的になります。

OSDごとの平均データ量 = クラスタの総データ量 / OSDの総数

この値が正規分布するということは、OSDによってデータ量の多寡があるということです。クラスタの総空き容量は十分なのに個々のOSDがいっぱいなため、それ以上のデータの追加が失敗する、つまり実質的な空き容量は理想的なものより少ないという事態が発生するのです。この実質空き容量はOSDごとのデータ量の分散が小さければ多くなり、大きければ少なくなります。

セッションの後半では、echoにおいてこの分散をいかに小さくするかという闘いの記録を共有してくれました。彼はまず従来から存在するOSD rewrightという仕組みをを使いました。これはOSDごとにどれだけのデータを保持するかの重みを設定するというものです。weightが大きければ配置するデータ量を多くして、小さければデータ量を少なくします。

これを利用してOSDごとの配置データ量を定期的に監視しておき、量が多いOSDについてはweightを低くして少ないOSDについてはweightを大きくする…といった処理をすれば分散が小さくなるとというのが狙いです。ただしこの方法はそれほどうまくいきませんでした。

続いて彼が試したのはLuminousから導入されたPG upmapという機能です。この機能は特定のPGに属するデータを特定のOSDに配置するというものです。これによってオブジェクトを空き容量が多いOSDに狙って配置するというわけです。このPG upmapは実によく働いて、最終的にechoは実質空き容量を6PB増やせたとのことです。OSDごとのデータ量の分散が、とくに大きなクラスタにおいて、いかに大きなインパクトがあるかがわかります。

f:id:cybozuinsideout:20190521133246j:plain
PG upmapの効果

Geographical Redundancy with rbd-mirror: Best Practices, Performance Recommendations, and Pitfalls - Florian Haas, City Network

sched.co

このセッションはrbd mirroringと呼ばれるrbdのリモートレプリケーション機能についてのものでした。

rbdにおける書き込みは通常次のような順で動作します。

  1. クライアントが書き込み依頼をする
  2. プライマリOSDに書き込みをする
  3. それ以外のレプリカを保持するOSDに書き込みをする

この処理は同期的に前から順番に実行します。

レプリケーションを実行するためにまず思いつくのはCRUSH ruleによってOSDのレプリカを複数のサイトに配置するように制約をかける方法です。これによってどこかのサイトが使えなくなったときでもすべてのデータについてのレプリカは別のサイトに存在する、というわけです。ただしこの方法は現実的ではありません。とくにそれはサイト間の距離が長いほど顕著です。

なぜかというと書き込みにおけるレイテンシは物理的な制約によってサイト間距離にしたがって長くなるからです。上述の通り書き込みは同期的なことより、レプリカ数が増えればレイテンシはさらに長くなります。このため、たとえば数百km離れた場所へのリモートレプリケーションは書き込み性能が(読み出しもですが)実用不可能な性能になるでしょう。

このような問題を防ぐために、Cephにはリモートレプリケーションのための専用機能があります。このためにはローカルサイトとリモートサイトに別々のクラスタを用意します。ローカルクラスタへの書き込みにおいては上述の書き込みパスに加えて、ステップ2の段階でジャーナルログにもデータを書き込みます。その後メインの書き込みパスとは非同期的にジャーナルログからリモートクラスタに書き込みをするというわけです。この方法を使えばレプリケーション処理はローカルクラスタに対する処理を邪魔しません。

ただしこの方法にも落とし穴があります。ジャーナルログからリモートクラスタへの書き込みそのものの性能インパクトは少ないのですが、ローカルサイト上の書き込みが通常のものに加えてジャーナルログにも書き込むことより、書き込みに余計に時間がかかってしまうのです。この性能インパクトが馬鹿になりません。

このことより発表者はリモートレプリケーションは安易に使うものではなく、定期的にrbdイメージのスナップショットをとってリモートサイトに転送したり、あるいはリモートレプリケーションを使うにしても何も考えずにクラスタ全部のデータをレプリケーション対象にするのではなく、大事なデータだけを対象にすべきということを述べました。

総評

CephaloconはCephがもともとニッチなソフトウェアであることもあって数百人程度の規模なのですが、個々のセッションを見るとOpen Source SummitやKubeConに負けず劣らずの盛り上がりを見せていました。また、絶対的なユーザ数が少ないこともあってか、開発者同士だけではなく開発者とユーザの距離が非常に近いというのも大きな特徴でした。このようなことは実際にイベントに参加しないと肌感覚としてなかなかわかりにくいので、今回参加した価値は大いにあったとと思います。今後もCephコミュニティがこのような雰囲気を保持しつつ、ますます活発になっていくことを願います。

KubeCon + CloudNativeCon Europe 2019 現地レポート 1日目

$
0
0

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

f:id:cybozuinsideout:20190522131244j:plain

今年もKubeConの季節がやってきました。今年も初回はヨーロッパです。我々はCo-located eventのCephaloconに2日間参加した後の参加となります。Cephaloconについてのレポートはこちらを御覧ください。

blog.cybozu.io

blog.cybozu.io

去年のヨーロッパ開催はコペンハーゲン(デンマーク)でしたが、今年はバルセロナ(スペイン)です。

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

Keynote

CNCF Project Update - Bryan Liles, Senior Staff Engineer, VMware

CNCF Projectのアップデートです。前回のNorth Americaからおよそ半年ですが様々な出来事がありました。 まずはSandbox, Incubator, Graduated projectsが更に増えたことです。現在はそれぞれ16, 16, 6 projectというこでCNCFが管理するProjectは合計で38になりました。ここでは2つほど簡単に紹介します。

f:id:cybozuinsideout:20190522024436j:plain

Sandbox: OpenEBS - Container Attached Storage

OpenEBSはMAYADATA社が開発した、独自の形式でKubernetesにPersistent Volumeを提供するソフトウェアです。以前の調査で、最終的にはNecoでの利用候補からは外していますが、CNCFによる運営となり成熟されたソフトウェアに進化したのではないかと密かに期待しています。

f:id:cybozuinsideout:20190522024235j:plain

Incubating: Helm Docs | Helm

HelmはKubernetesのパッケージマネージャーであり、生のmanifestを用意せずともテンプレート化およびパラメータ化された形式でLinuxのパッケージ管理システムのように自由にデプロイすることが可能です。先週3.0.0-alpha.1がリリースされ、Tiller podのデプロイが不要となりました。Helm界隈では一つの大きなターニングポイントであり、これまではTiller podのデプロイをしておかないとhelmによるアプリケーションのデプロイができませんでしたが、一つの大きな依存がなくなったため、より快適なデプロイが可能になるでしょう。

Getting Started in the Kubernetes Community - Lucas Käldström, CNCF Ambassador, Independent & Nikhita Raghunath, Software Engineer, Loodse

若いCNCF Ambassador(大使)二人が、Kubernetes Communityの魅力について語っています。NikhitaはSIG Community ExperienceのLeadをしており、Kubernetes Communityのコミュニケーションの円滑を行っています。Individual(個人)の貢献は仕事の上でのステータスを示すことができるため、キャリアアップにも効果的であること、イベントの参加によって新しい発見があることなどが挙げられています。また、コミュニティの中で自分を向上させるためには「SIG(分科会)のメンバーと会話すること」「KEP(Kubernetes Enhancement Proposals)に欲しい機能や改善を提案すること」です。もし仕事でKubernetesを使っているけど足りない機能を拡張したいなんてことがあれば、Forkして実装せずにまずはUpstreamに関わることが重要と思いました。

f:id:cybozuinsideout:20190522043909j:plain

LucasはSIG Cluster LifecycleのChairを務めています。彼のお話では「どのようにKubernetes Communityに入ったか」というテーマがとても印象に残りました。彼は高校生のとき、Raspberry Piを使ってKubernetes clusterを構築しました。当時のKubernetesはまだIntelベースのビルド体系になっていたため、プラットフォーム非依存化を目指してPull Requestを作成しました。Kubernetes 1.14ではWindows NodeがGAとなり安定してWindowsでもKubernetes clusterが組めるようになったのですが、彼のようなちょっとしたきっかけが周りの考え方の変化に繋がったのではないかと感じています。

f:id:cybozuinsideout:20190522044256j:plain

Democratizing Service Mesh on Kubernetes

サービスメッシュにも標準化の時代がやってきます。Microsoft社はService Mesh Interface(SMI)本日のプレスリリースと合わせて発表しました。汎用のAPIインタフェースを提供することで、SMIは開発者が特定の実装に縛られることなくサービスメッシュ機能を使用できるようにします。 アプリケーションを変更しなくても、実装を実験したり変更したりできます。 SMIは、Kubernetes標準のリソースであるIngressやNetworkPolicyのような考え方とほとんど同じです。 SMI仕様では、Service Mesh providerが独自の実装を提供できるようにする一連の共通APIを定義しています。 つまり、providerはSMI APIを直接使用するか、またはoperatorを構築してSMIをNative APIに変換できます。

f:id:cybozuinsideout:20190522064737j:plain

Kubernetes Project Update - Janet Kuo, Software Engineer, Google

Kubernetes projectのアップデートです。2003年のGoogleのBorg projectから始まり、cgroups、Container、Dockerの登場という経緯を経て、スタートから5年が過ぎたKuberenetes。現在、Kubernetesは1億GitHub projectある中で2番目のPull request数、4番目のIssue数を誇る巨大プロジェクトとなりました。

f:id:cybozuinsideout:20190522065712j:plain

Kubernetes 1.14では安定化・成熟化に注力したバージョンでした。次のバージョンで目指すポイントは拡張性の向上として現在betaであるCRDの対応化、スケーラビリティの強化などがあります。後者については例えばNode Statusを定期的にチェックする場合、control plane上のetcdに状態を記録します。しかしNodeが5000台あると、300-600MB/分のデータ保存が必要になります。etcdへの負荷が非常に大きくなるため、改善が必要です。解決方法としてNodeLeaseという機能の提案がKEPとして挙げられています。

enhancements/0009-node-heartbeat.md at master · kubernetes/enhancements · GitHub

CFP Tracks

P2P Docker Image Distribution in Hybrid Cloud Environment with Kraken - Yiran Wang & Cody Gibb, Uber

Uber社が開発したP2P型コンテナイメージ配布システム「Kraken」の紹介です。彼らは以前からコンテナイメージ管理に対する最適化を進めています。Docker Registryのスケールなどを検討したこともあったそうです。KrakenはP2PによるLocal Docker registry間の接続を行いますが、Node〜Nodeの接続先を決めるのは"Random regular graph"というグラフ理論(離散数学)のアルゴリズムを元に、近距離なNodeを見つけて、コンテナイメージを共有します。共有中のIndex情報を外部に記録しておく必要があり、現在はS3 / HDFSに対応しています。またすべてのNodeが同じコンテナイメージを保存するため、時として不要なイメージが残り続けてNodeの空き容量を使いすぎることがあるでしょう。そこで、保存されたイメージにTTLを用意しておき、一定の時間使われなくなったら自動的に削除する仕組みが用意されています。

Node数が増えるに連れて大量のdocker pullが発生し、UpstreamのDocker registryに大きな負荷を与えます。初回の取得だけUpstreamから行ったら、そこから他のNodeに配布するというアプローチによって、全体的に大幅な帯域削減が可能になります。またP2Pによるデータセンター内配布は他の用途にも使える可能性があることを示唆しました。

f:id:cybozuinsideout:20190522063730j:plain

会場の様子・まとめ

今回は参加者7700人で去年のNorth Americaに近い混雑となっています。 会場の移動も時間を要しますし、疲労によってなかなか集中して聴けないセッションも出始めてきました。 また一つのTime slotで最大20セッションが同時に行われているため、目的を絞ったり、複数人で参加して分担して聴いて回る事が必要になってきます。

f:id:cybozuinsideout:20190522045255j:plain

f:id:cybozuinsideout:20190522073022j:plain

一方で世界的にKubernetesの需要が爆発的に増えているのがよくわかります。 コンテナオーケストレーションのデファクトスタンダードとなったのが改めて感じました。

f:id:cybozuinsideout:20190522045628j:plain

Kuberenetes projectは開始から5年が過ぎました。上のKeynoteでも触れていますが今のKubernetesは本番稼働として十分に安定しています。そして次は拡張性。毎年Kubernetesの目指す先が変わっていくので今後の成長も期待できると確信しています。

KubeCon + CloudNativeCon Europe 2019 現地レポート 2日目

$
0
0

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

昨日に引き続きKubeConの現地レポートをいたします。本日はNecoが注目しているRookについてのセッションがたくさんありました。先日"Production Ready"と宣言されたv1.0.0がリリースされたことによって、その注目度の高さがますます上がっていることを感じます。

本記事ではRookについてのセッションの中から面白かったものについて紹介いたします。

f:id:cybozuinsideout:20190523103545j:plain
二日目の怒涛のRookセッションたち

Rookがどういうもので、かつ、前回KubeConであるKubeCon + CloudNativeCon North America 2018においてどういう状況だったかについては以下の記事をごらんください。

blog.cybozu.io

昨日の記事については以下の記事をごらんください。

blog.cybozu.io

Keynote: Debunking the Myth: Kubernetes Storage is Hard - Saad Ali, Senior Software Engineer, Google

sched.co

本セッションのテーマはKubernetesでストレージを扱うのは難しいというよく言われること(発表者に言わせると"神話")についてのものでした。

開幕早々彼は「Kubernetesのストレージが難しいのではない、ストレージが難しいのだ」と述べました。これには私はうなずくばかりでした。ストレージは本質的に複雑であって、Kubernetesはその複雑さのうちの一部を引き受けて楽にしてくれるだけであって、何も考えずに楽に使えるようにしてくれるわけではありません。これはKubernetesのストレージだけではなく、Kubernetesについても同じことが言えるとわたしは思っています。

続いて彼は複雑な問題にどう立ち向かうかについて述べました。その方法とは大きな問題を分割して複数の扱いやすい問題に落とし込むことです。ストレージについては次のように分割していました。

  • Select: どのストレージを使うのかの選定。オブジェクトストアやRBD、ファイルストレージ、およびブロックストレージなど
  • Deploy: ストレージをデプロイする
  • Integrate: ストレージをクラスタから使えるようにする
  • Consume: ストレージをクラスタから使う

SelectについてはとくにKubernetesは関係なく、自分でやるしかないんだというメッセージを発信していました。

最後は「ストレージは複雑である、Kubernetesはそれを扱いやすくする」という言葉で締めくくられました。本セッションはストレージに限らず、技術者が本質的に困難なことをツールのせいにして自己弁護をするのを戒める示唆に富んだ非常によいものでした。

Data Without Borders - Using Rook Storage Orchestration at a Global Scale - Jared Watts, Upbound

sched.co

本セッションはRookを使ってマルチサイトストレージをRookを使ってオーケストレートするというものでした。

Rookはさまざまなストレージプロバイダを扱えますが、本セッションの対象はマルチサイトで動くことを考慮しているCephCockroachDBEdgeFSについてのものでした。この後に彼はそれぞれについて簡単な紹介をしたうえで、マルチサイトのEdgeFSについて注目して話を進めました。

Rook自身は地理的に同じ場所に構築されたクラスタを扱うものであり、それらクラスタを管理するためのオーケストレータは依然必要です。このオーケストレータについて彼はKubefedCrossplaneを挙げてました。

セッションは次のようなデモで締められました。

  1. EKSを使って2つのサイトに仮想マシンを立ち上げる
  2. それぞれの上にRookを使って立ち上げたEdgeFSを使ってEdgeFSクラスタを作る

CLIのデモなのでひたすら地味なのですが、数個のYAMLを読み込むだけでマルチサイトなクラスタできる様子がわかって感心しました。

Deep Dive: Rook - Jared Watts & Bassam Tabbara, Upbound

sched.co

本セッションはRookについての詳しい情報を提供するというものです。

まずはApache Cassandraのストレージドライバについて紹介がありました。Cassandraの機能そのものの説明の後に、Cassandraのノード、ラック、データセンターといった概念をどのようにKubernetesのリソースにマップするかについて触れました。

現在のところRookを介してCassandraを使う場合は次のようなことができるとのことです。

  • クラスタの作成、ブートストラップ
  • オートスケール
  • 故障したノードを別のもので置き換える

最後には前述のセッションと同様EdgeFS、およびCrossplaneとRookを使ったマルチサイトストレージについて言及がありました。後者についてはRookを用いてCrossplaneの拡張をするという実装の話まで踏み込んでいました。前回までのKubeConではマルチサイトストレージに関する言及は無かったので、今後はこの領域にも注力するということなのでしょう。

なお、本セッションは超満員で、参加者が部屋の外にあふれ出すほどでした。去年のKubeConでRookに関するセッションはこれほどの混雑はまったくなかったので、Rookに対する注目度がどんどん高くなっていることがわかりました。

f:id:cybozuinsideout:20190523110117j:plain
本セッションの様子

KubeCon + CloudNativeCon Europe 2019 現地レポート 3日目

$
0
0

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

楽しかったKubeConもいよいよ最終日となりました。これまでの現地レポートはこちらになります。

blog.cybozu.io

blog.cybozu.io

それでは今回も数多くのセッションの中で印象に残った発表などを簡単に紹介していきます。

Keynote: Metrics, Logs & Traces; What Does the Future Hold for Observability? - Tom Wilkie, VP Product, Grafana Labs & Frederic Branczyk, Software Engineer, Red Hat

Observabilityの将来について語ります。Observabilityとして3つの柱として掲げたのが、メトリクス(監視)、ログ(収集)、トレースです。そして3つの予測を立てました。

f:id:cybozuinsideout:20190523205704j:plain

  1. 柱同士にもっと相関関係を。例えばPrometheus(メトリクス) + Grafana Loki(ログ収集)、Elasticsearch(ログ)+Zipkin(トレース)といった組み合わせです。例えば同じようなUXを持ち、あるタイムラインのメトリクスとログを参照できるようにするといったことが挙げられます。
  2. 新しいシグナルと分析。継続的にプロファイリングを行うことで、障害や警告などの予測を立てる。
  3. Indexingをしないログ収集。Indexingをした環境で収集されたログを検索する場合、ソフトウェア専用のQueryを組み立てて絞り込みを行います。一方でgrepコマンドでシンプルにパイプを使った絞り込みの方がLinuxユーザーにとっては慣れ親しみを感じます。簡単に絞り込みができ、Indexingを必要としない使い勝手が必要なのかもしれません。

Grafana Loki: Like Prometheus, but for Logs - Tom Wilkie, Grafana Labs

Keynoteに登壇したTom Wilkie氏によるLokiの紹介とデモです。PrometheusおよびCortexのMaintainerでもあります。

Lokiはスケーラビリティと高可用性を持つログ収集システムで、KubeCon North America 2018で発表されました。代表的なログ収集システムではCNCF Graduated projectのFluentdがあります。ログの収集後に検索できるようにするまでの手順としては、ElasticsearchでIndexingしておき、他のWeb UIでで検索するというのがよくある手順です。実現するにはIndexingが必須で、さらにFluentdにおいてはFilterを定義してログの種類を教えておく必要があります。すなわちそのような構成を構築するだけで面倒な点が多いのが課題です。それらの課題を解決するのがLokiです。LokiはPrometheusにインスパイアされており、以下の特徴があります。

  • PrometheusのようなLabel, tagベースの検索。外部サービスによるIndexingが不要。
  • PrometheusのようなService Discoveryによって自動的にPodを検出し、そのログを収集する(promtail)。
  • Grafana Labsによる解決でWeb UIの親和性も高い。
  • LogQLによる絞り込みを行う。grepのような形で条件をつなげることができる。

f:id:cybozuinsideout:20190524014159j:plain

f:id:cybozuinsideout:20190524014308j:plain

このセッション会場は一番大きく、ほぼ席が埋め尽くされるほどの人気でした。当日のKeynoteに感銘を受けた人や以前から気になっていた参加者が集まっていたのかもしれません。デモやTomの人柄もとてもユニークでした。

Kubernetes Scalability Definition Evolution - Wojciech Tyczynski & Andrzej Wasylkowski, Google

Kubernetesはスケーラビリティのあるコンテナオーケストレーションですが、ではスケーラビリティとは何なのかという問いに対して答えるというセッションでした。まずSLI(Service Level Indicator)やSLO(Service Level Objecive)を満たせばその条件においてスケーラビリティが可能と約束することができました。2015年と2017年においては以下のようなSLIやSLOを満たせばスケーラビリティがあるとしていました。

2015年

  • API reqeustが1秒以内で99%返ってくること
  • 予めコンテナイメージをPullしておいた環境でコンテナが5秒以内で99%起動すること

2017年

f:id:cybozuinsideout:20190524015834j:plain

現在は多くのSIG(分科会)があり、いろいろな要因によって条件の達成の難易度が上がるかもしれないし、条件として過不足があるかもしれません。それを見直してより正確なSLI/SLOを定義することになりました。具体的には正確かつ矛盾が無く、観点がユーザ志向であり、かつ、テスト可能なものとしました。

さらに定義を公開した後にサービス利用者からのフィードバックを受け取り、それをもとにシステムを改善するといったサイクルを回すことがよりよいサービス提供の要となります。

今回の例ではKuberenetesの成長に合わせた定義の変更でしたが、モノリシックなインフラからマイクロサービスに切り替える際にも、SLI/SLOを再定義する必要があると感じました。

Secrets Store CSI Driver-Bring Your Own Enterprise Secrets Store to K8s - Rita Zhang, Microsoft & Anubhav Mishra, HashiCorp

CSIによるSecret Volume mountの紹介です。

CSI(Container Storage Interface)とはKubernetes、Mesos、Docker、Cloud Foundryといったコンテナオーケストレーションにおいてコンテナで使用するVolumeの形式を標準化した仕様です。KubernetesにおいてはサードパーティのStorageClassを使用する場合、まずすべてのNodeにAttach/Mount/Detach/Unmountをするためのプログラムをインストールしておく必要があります。例えばCeph RBD(Block Device)を使いたい場合はrbdコマンドを全Nodeに用意します。

Volume操作をするプログラムのインストールという操作をなくし、より効率よくVolumeを提供かつ標準化のために作られたのがCSIです。Kubernetes v1.13でGAとなり、誰でもKubernetes向けにCSI driverを作る環境がすでに整っています。

このセッションではCSI driverでsecretを読み込む方法について解説しています。CSIによるVolumeはPersistentVolumeおよびPersistentVolumeClaimのみしか使えなかったのですが、EphemeralなVolume、例えばSecretリソースのMountもCSI driverでできるようにしようという機能があります。これをCSI Inline Volumeと呼んでいます。

KEP: https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/20190122-csi-inline-volumes.md

(KEPとはKubernetes Enhancement Proposalsの略で、改善したり新しい機能を追加したいときに提案するドキュメント)

この機能はKubernetes v1.15-alpha.2以上でFeatureGate(beta, alphaの機能をフラグで有効にする機能)で利用できます。なお本記事執筆時点におけるKubernetesの最新版はv1.15-alpha.3です。

デモでは3種類のSecretをCSI driverによるMountで読めるようにするというものでした。

f:id:cybozuinsideout:20190524023610j:plain

現在はいずれもAlphaであり、またKubernetes APIからSecretを作成して各サービスのVaultに保存するといった事もできません。CSIが登場する以前はFlexVolumeという形式で、上述の通り全NodeにVolume操作のプログラムをインストールして利用しています。現在はFlexVolume形式が主流ですが、CSI driverはまだまだ少ないため、コミュニティとしてもFlexVolme相当の機能開発をしている段階であると感じました。一方でこのようなまさに開発中のお話を聴けるのが、参加の醍醐味でもあります。

会場の様子

Kubernetes生誕5周年ということで、感謝のメッセージを綴るWallや、歴史をまとめたブースがありました。

f:id:cybozuinsideout:20190524055043j:plain

青いかけらのようなものは、自分がどの頃にKuberenetesに触れ始めたのか参加者が貼り付けたものです。ちなみに私の場合、etcd v2でRBACがデフォルトではなかったころでした。おそらく2016年ごろですね。

f:id:cybozuinsideout:20190524055134j:plain

最後に

最後のKeynoteで今後の予定が公開されました。KubeCon Europe 2020はアムステルダム(オランダ)です。

f:id:cybozuinsideout:20190524012805j:plain

バルセロナは食事が美味しいし安い。そしてこの時期は21時過ぎまで太陽が照り続けてる街です。 4回目の参加でしたが、今回が最高だったと思います。

KuberenetesおよびCNCF Projectの今後の成長も期待しつつ、次回も現地からのレポートをお送りしたいと思います。5日間連続に渡って読んでいただきありがとうございました!


多言語サイトで言語ごとの内容を同期するのが楽になった話

$
0
0

こんにちは。開発部テクニカルコミュニケーションチームの澤井です。

サイボウズでは昨年、ヘルプサイトの記事更新の高速化や翻訳の効率化を目的として、ヘルプ基盤を変更しました。詳しい内容は以下のページで紹介しています。

これらの変更により、特にサイトの多言語化業務においてさまざまな改善効果を実感しているので、今回はその紹介をしたいと思います。

多言語サイト運用で課題となっていたこと

サイボウズのヘルプサイトのページ数は1万を超え、日英中3言語で展開しています。また、開発サイクルが短くなっていることから、日本語の記事更新はほぼ毎日行われています。各言語でなるべく早く最新のコンテンツを公開するにあたり、以下のような課題がありました。

  • 差分管理が大変
  • 翻訳結果の取り込みに時間がかかる

それぞれ、どういうことか説明します。

差分管理が大変

翻訳者に翻訳依頼を出す際、翻訳対象テキストを伝える必要があります。
ヘルプ基盤変更前は、更新した日本語のページをPDFに保存し、変更箇所をハイライトしたり、注釈をつけたりすることで翻訳者に変更点を伝えていました。また、販売地域ごとの書き分けもこのPDFファイルの中で翻訳者に伝えていました。
書き分けとは、例えば「メールワイズ」は日本国内でのみ販売している製品なので、日本リージョンの中国語サイトには記述が必要ですが、中国リージョンの中国語サイトでは非表示にしなければならない、といった制御のことです。

PDFファイル画像
PDFファイルで変更点を連絡
このやり方だと、変更するページの数だけPDFファイルを作成しなければならず、変更ページ数が多いと作業が膨大になってしまう問題がありました。翻訳者も、全てのPDFファイルを開いて変更箇所を探した上で翻訳しなければならず、実質の翻訳業務以外の管理業務にかなり時間を奪われる状態でした。

さらに、翻訳依頼者が翻訳者にPDFファイルを渡す際に最新でないファイルを渡してしまう、翻訳者が変更箇所を見落として訳漏れが発生する、などのミスもたびたび発生していました。これらのミスを防ぐため、翻訳依頼者、翻訳者の両方が確認作業に多くの時間を割いている状態でした。

翻訳依頼
日本語ヘルプ担当者が翻訳依頼用ファイルを作成

翻訳結果の取り込みに時間がかかる

翻訳者は、PDFファイルで翻訳箇所を確認した後、翻訳作業をプレーンテキスト形式で行っていました。翻訳結果をヘルプサイトのコンテンツとして公開するためには、翻訳結果をCMSに反映し、HTML形式に変換する作業が必要です。ヘルプの基盤を変える前は、翻訳者がCMSに翻訳結果を手作業で入力することでこの変換を行っていました。 また、たとえば同じ中国語の翻訳ファイルであっても、販売地域によってCMS上で記述を書き分ける作業が合わせて必要でした。
この、CMSに手作業で翻訳結果を入力する作業と販売地域ごとの書き分けの作業のために、翻訳結果の取り込みに多くの時間がかかっていました。

翻訳結果の取り込み
翻訳者がCMSに手作業で翻訳結果を入力

いかに課題を解決したか

この2つの問題をどのように解決したか説明します。

CAT(翻訳支援)ツールの導入

CAT(翻訳支援)ツールとは、翻訳作業を行うエディタ機能、過去訳を参照できる翻訳メモリ、ツール内での機械翻訳の使用などを一元的に行えるツールです。SDL Trados、memoQ、Memsourceなどが人気のようです。
サイボウズではヘルプ基盤の変更に伴い、Memsource(メムソース)を導入しました。Memsourceはチェコの企業発のクラウド型CATツールです。
これにより、悩みの種だった差分管理がとても楽にできるようになりました。

Memsource操作画面
Memsource操作画面
翻訳対象のファイルをMemsourceに取り込むと、画像のように翻訳用エディターに翻訳対象テキストが表示されます。このとき、過去に既に翻訳された文章は翻訳メモリとして活用され、Memsourceが自動的に翻訳します。つまり翻訳者はシステムの表示に従って原文の変更箇所のみ翻訳すれば良いのです。

この機能のおかげで、これまで翻訳依頼者が行っていた「ここを変更したので翻訳してください」という連絡が不要になり、翻訳者は翻訳個所をいちいち探すことなく集中して翻訳に取り組むことができるようになりました。このおかげで、翻訳にかかる期間が短縮されました。

さらに、用語の過去訳が常に参照できるおかげで表記の統一がしやすくなり、文書品質が向上したことも導入の大きなメリットでした。

マークダウン移行による翻訳取り込みの効率化

翻訳結果取り込みを楽にしてくれたのが、ヘルプのマークダウン移行です。

翻訳の取り込みでボトルネックになっていたのは「CMSに手作業で1ページずつ、翻訳結果や販売地域ごとの書き分けを入力しないといけない」点でした。「HTMLファイルのまま翻訳してHTML/CSSファイルの中で地域ごとの書き分けを指定し、そのまま公開することはできないの?」と思うかもしれませんが、このやり方は、意図しない内容が検索結果に表示されてしまう、「隠しテキスト」としてGoogleからの評価が下がってしまうなどの弊害があるため、好ましくありませんでした。

根本の1つの翻訳ファイルの中で地域ごとの書き分けを指定でき、システムがそれを変換して各地域ごとのHTMLファイルを生成してくれると一番楽に運用できますが、これを実現してくれたのが静的サイトジェネレーターHUGOのマークダウン拡張機能です。翻訳ファイルの中で {{% disabled US %}} のようなショートコードを使って販売地域の書き分けができるようにしました。

Memsourceは、翻訳対象ファイルをマークダウンで読み込むと、翻訳結果ファイルもマークダウンで出力できるので、この出力ファイルをGitHubに保存するだけで、各地域ごとのHTMLファイルが自動的に生成、公開されるようになりました。(参照リンクを言語ごとに変える処理など、テキストの翻訳だけで対応しきれない部分は独自の変換ツールを使用しています。)

上記の対応によって、定型業務がどれくらい削減されたか示したのが下図です。原稿作成から公開までにかかる時間はほぼ半分に減りました。

稼働削減イメージ
稼働削減イメージ

変更後の翻訳フロー

ヘルプ基盤変更後の翻訳フローは、以下のような感じです。

基盤変更後の翻訳フロー
基盤変更後の翻訳フロー

  1. 日本語ヘルプ担当者が翻訳依頼用のブランチを作成し、翻訳者に翻訳開始を依頼します。

  2. 翻訳者は、GitHubからマークダウンファイルをダウンロードし、Memsourceに読み込みます。

  3. 翻訳者はMemsourceの翻訳エディター上で翻訳作業を行います。

  4. 翻訳が終了したら、Memsourceから翻訳結果のマークダウンファイルを出力し、GitHubに反映します。

  5. 自動的に販売地域ごとのHTMLファイルが生成され、公開されます。

具体的なやりとりの内容で説明すると、日本語ヘルプ担当者が「日本語が確定しましたので、このブランチの翻訳をお願いします!」と依頼したら、翻訳者が翻訳終了後に「翻訳を反映しましたので、プルリクエストの承認お願いします!」という返事をする、というシンプルなフローに落ち着きました。

これまで当たり前だった煩雑な確認や連絡が一切不要になったので、最初にこのフローを実施したときには「本当にこれで大丈夫なの?」と心配になったほどでしたが、翻訳結果はもちろん、問題なく公開されました!

改善効果

ヘルプ基盤変更の前と後で、原稿作成から翻訳結果の公開までにかかる時間はほとんど半分になりました。これまで、翻訳にかかる稼働を考えて記事の改善に心理的なハードルを感じる場面がありましたが、今はまったく気にすることなく小さな改善も大きな改善も実施できています。

また、定型業務に忙殺される状態を脱して時間が有効に使えるようになったことで、コンテンツ内のスクリーンショットを増やしてより直感的に理解しやすい内容にしたり、Google Analyticsなどの分析ツールを活用してよりユーザーにとって価値のあるコンテンツを検討したりするなど、ヘルプの品質を高める取り組みにより重きを置けるようになりました。

現在テクニカルコミュニケーションチームでは、文書校正のルールを検討して自動化するなどの取り組みを進めています。機械翻訳の活用も進めているので、これらの取り組みについても別の記事でご紹介できればと思います。

最後に

テクニカルコミュニケーションチームのメンバーや翻訳者は大多数が非エンジニアで、マークダウンやGitHubを本格的に使ったことがないメンバーもいました。そのため、新しいツールを使いこなすための勉強会やハンズオンなどにはそれなりのコストがかかりました。今回の改善の学習コストが高いことは否定できませんが、今は全員が問題なくこれらのツールを使いこなし、スピーディーなヘルプ更新や翻訳が行えています。コストよりも、得られるリターンがとても大きいことを実感しています。

この記事が、多言語サイトを管理するチームの方々の何かの参考になればとても嬉しいです。

大阪にもCy-PSIRTあり

$
0
0

まいど! Cy-PSIRT(Cybozu Product Security Incident Response Team)の長友(@naga_hito)です。
今年1月、私が大阪に異動し、なんと大阪にもPSIRTができました。これで国内3拠点、国外拠点を合わせるとなんと5拠点(東京・大阪・松山・上海・ホーチミン)にPSIRTができたことになります。私が入社したころ(3年前)は東京・上海にしかPSIRTはなかったので、当時では考えられなかった大所帯です。
この記事では大阪拠点に異動したことで感じたリモートチームでの働き方について書いていこうと思います。

異動のきっかけ

体力がそこまでない私にとって「出社」というイベントはどうしてもつらく厳しいものでした。朝は眠いし、駅は混んでるし、満員電車に押し込まれるし、なにもいいことがありません。出社時間の変更など、対策をいくつか講じて多少は楽にはなりましたが、それでも厳しいことには変わりがなくどうしたものかと考えていました。そんな時「大阪はいいぞ」との声を社内外から聞きました。そもそも大阪は私にとってゆかりのある土地です。前々から行ってみたいとは思っていたので、大阪拠点に異動したい旨を上長やチームメンバーと相談しました。それから数カ月後、ひとまずトライアルで2週間ほど大阪で勤務しました。
結果、問題なく働けそうだということを確認したうえで正式に大阪オフィス異動となりました。

リモートチームは一日にしてならず

もともと私は年に何度かオフィス以外の場所で仕事をしていましたし、なにより松山や海外のメンバーとも働いているし、大阪行っても大丈夫でしょう、困ることなんてないでしょう、と楽観的に見ていた部分がありました。しかし大阪に移って今更ながら気づいてしまいました。「リモートチームは一日にしてならず」だと。
遠く離れた拠点同士で働くようになっていくつか困った事態が発生しましたので、それぞれ実例を挙げながらお話していこうと思います。

相談事どうしよう

これまで東京拠点で働いていた時、困りごとや相談したいことが発生した時はチームメンバーにすぐ口頭で直接相談していました。けれど今は大阪です。チームメンバーは私以外に誰一人いません。困りました。誰にも直接口頭で尋ねられません。
口頭で聞けないならチャットや画面越しで聞けばいいじゃない、ということでチャットやテレビ会議を使って解決しました。ちょっとした相談事はほとんどチャットで済ませています。
最近、iPadを日本国内のそれぞれのチームに設置し、常時テレビ会議が接続されている状態を実現することができました。これで、以前東京拠点にいたときのように気軽に話しかけることができるようになりました。
また、ちょっとしたわからないことをkintone上の「分報 *1」に書いておくと、チームメンバーに限らず、詳しい誰かが回答してくれることがあり、とても助かっています。JavaScriptの書き方やテレビ会議システムのTips、食べこぼしをしたときの染み抜きのおすすめなど、詳しい人はどこかにいるものです。
腰を据えて議論したい内容は事前にkintoneのアプリやスレッドでアジェンダをまとめておき、関係者を集めてテレビ会議をするようにしています。こうしておくと議論が迷子になりにくいですし、関連する内容について事前に調べておくこともできます。これは以前からやっていたことではあるのですが、結構重要だと異動して改めて感じました。対面で話したり仕事したりしているわけではないせいか、なぜか抜け落ちてしまう情報が生まれてしまうことがあります。議論の前提を事前に埋めておくことで、抜け落ちかけているちょっとした情報を事前に調べておいたり、打ち合わせの間に拾い上げやすくなっているような気がします。

レビューどうしよう

たとえばドキュメントやブログ、脆弱性報告へのお返事など、Cy-PSIRTは対外的な文章を書くことが多いチームです。そのため、文章を書いたら複数名にレビューを依頼する、というのが基本になっています。ただどうしても忙しい時期になるとだれもレビューに手が回らない、という事態が発生してしまいます。
困ったなあ、と考えていたら他のチームがモブプログラミングをしていることを思い出しました(参考:他のチームを体験する「モブプロ開発合宿」を開催しました - Cybozu Inside Out | サイボウズエンジニアのブログ )。これは使えそうな気がしたのでPSIRTメンバーのスケジュールを押さえ、その時間で全員で同時にレビューしてもらうことにしました。モブプログラミングならぬモブレビューです。すると、かなり時間がかかりそうだと思っていたことが数時間で終わってしまいました。これがきっかけかどうかはわかりませんが、PSIRT内では複数名で作業することが増えました。

声かけていいかわからない

同じ物理オフィスにいると忙しそうかそうでもなさそうか、場の雰囲気的に発言していいかよくないか、みたいなところがおおよそ見てわかります(もちろん、わからないこともありますが)。けれどテレビ会議やチャットではそのあたりの情報がどうしても抜け落ちてしまい、距離感や空気感がつかみづらくなる印象があります。そのため「今私は発言していいのだろうか」「今声かけていいのだろうか」という疑問が発生してしまいます。困った。
これを解決するためには、とりあえず話しかけてみるのが一番でした。空気がよくわからなくてもとりあえず話しかければなんとかなるというのが個人的な印象です。これができたのは、忙しいときは「ごめん無理です」とお互い言い合える関係になっていた、というのが大きいかもしれません。

大阪事情

ここからは、リモートという話を離れて大阪拠点で働くことについてお話をしようと思います。

拠点について

大阪の開発・運用にかかわるメンバーは様々なロールのメンバーがいます。kintone開発、クラウド運用、Neco、情シスなどなど、本当に様々です。また、オフィスの規模が東京拠点よりも小さいため、開発以外のメンバーと関わりを持つ機会が増えました。営業やシステムコンサルティングといった様々なバックグラウンドを持ったメンバーとお弁当やスイーツを食べながら話をしていると、いろいろと見えてくるものがあって面白いですし、なにより仕事が進めやすくなったと感じています。 「カフェ部」という社内の部活動でおいしいコーヒーや紅茶とスイーツをみんなでいただくというイベントが不定期に開催されるのですが、参加するととても幸せな時間を過ごせます。 カフェ部の風景

勉強会について

東京より頻度は少ないです。以下に「セキュリティ」で検索した例を挙げます。

アツさに関しては東京と変わりませんでした。ただし、待っていても情報は全く来ないので、自分で情報を集めたり咀嚼したりする力は東京にいるときより必要になったという印象です。
また、東京の拠点で実施される外部の勉強会を、主催の方のご厚意で大阪オフィスと中継して実施することができました。大阪にいるとなかなか聞けない話が聞けて非常に面白かったです。今後もご依頼いただければ各拠点間の中継を前向きに検討したいと思いました。会場の貸し出しについて詳しく知りたいかたはこちらをご確認ください。

おわりに

働きたいところで働けてとてもうれしい反面、いろいろとお困りごとが発生しています。これからもひとつひとつ解決しながらすすめていきたいと思います。 サイボウズではリモートチームでも楽しく働けるメンバーを募集しています。詳しくはこちらをご確認ください。

*1:「今やっていること」や「困っていること」「考えていること」などを思いついたタイミングで書いておくこと。イメージとしてはTwitterのような感じです。

Go 製ソフトウェアでメモリ使用量の多い関数を特定する

$
0
0

みなさんこんにちは.SRE チームの内田(@uchan_nos)です.

この記事では Go 製ソフトウェアのどの関数がどれだけメモリを消費しているかを調べる方法を説明します.

Go 製ソフトウェアのヒープメモリの消費量を調べる方法はたくさん解説されているものの,スタックメモリの消費量について調べる方法を説明したサイトを見つけることはできませんでした. この記事では主にスタックメモリの消費量を調べる方法を説明します.

背景

SRE では Go 言語で自社データセンター向けのツール群をたくさん作っています. その中のソフトウェアの 1 つが,本番運用中に予想外にたくさんのメモリを使用してしまうという問題がありました. どの関数が原因なのかを突き止めるために,関数単位でメモリ使用量を調べる必要があります.

ソフトウェアが使っているメモリ量の概況は,Linux であれば top コマンドで調べることができます. コマンド出力のうち VIRT が確保済み仮想メモリの量,RES が使用中の物理メモリの量を表します. 今回は RES の量が予想以上に大きくなっており,その原因を調査するというのがミッションです.

Go 製ソフトウェアであれば pprofパッケージにより簡単にヒープメモリの使用状況は調査ができますので,まずは pprof を使ってみました. しかし,pprof によるヒープメモリの調査結果と top コマンドの RES の値が大きく食い違っており,原因はヒープではないと分かりました.

pprof の他に expvarというパッケージを使い,メモリの各種統計値を見てみました. するとスタックメモリの使用量を表す StackInuse 項目が,ヒープメモリの使用量を表す HeapAlloc に比べて大きな値になっていることが判明しました. RES の値が StackInuse + HeapAlloc に近い値だったため,メモリ使用の大半がスタックによるものだと推測し,スタックの使用状況を調べることにしました.

スタックの使用状況の調べ方

今回私が思いついたスタックの使用状況を調査するアイデアはこうです.

  1. 実行バイナリから,関数毎のスタック使用量を見積もる
  2. goroutine のスタックトレースを取得する
  3. 両者を組み合わせてどの goroutine が何バイトのスタックを使用しているかを得る

1 をどうやるかがこの記事の主題と言っても過言ではありません. アイデアは単純で,sub rspというアセンブリ命令に着目するのです.

Go のコンパイラは,スタックフレームを確保するためにスタックポインタ(RSP)から必要な大きさを減じるための命令を関数の処理の先頭に埋め込みます. それは典型的に次のようなアセンブリ命令になります.

sub  rsp, 0x10

上記の例では RSP から 16 を引きます.すなわち,スタック領域として 16 バイトを確保するということです. Go コンパイラが生成した関数は,ぱっと眺める限り,関数の先頭でスタックフレームを確保した後は他の関数を呼ぶまで RSP は変化しません. 従って,実行ファイルの逆アセンブル結果から各関数について sub rspを探せば 1 はほぼ達成です. 後は,call命令のための 8 バイトを加算するなど,細かい調整をすれば,割と良い精度で関数のスタック使用量を見積もれます.

2 は簡単です.pprof を用いて /debug/pprof/goroutine?debug=1とすれば得ることができます.

3 は,基本的には各 goroutine のスタックトレースに登場する関数のスタック量の総和を取れば計算できます. 後は細かい調整です. goroutine のスタックの初期値は 2KiB1ですから,関数のスタック量の総和が 2KiB より小さければ 2KiB に切り上げます. スタックが溢れそうになると 2 倍ずつに増えてきます2ので,計算値が 2KiB より大きければ 2 のべき数に切り上げます.

gostackamount

以上のようなアイデアを手動実行するのは非現実的です. 実際にはツールを作っていますので,OSS で公開しました. 是非ご利用ください.

https://github.com/uchan-nos/gostackamount


  1. src/runtime/stack.go_StackMin定数により決まる

  2. src/runtime/stack.gonewsize := oldsize * 2

AWS CloudFormationのカスタムリソースでRDSやElasticsearchをアップデートする仕組みを作る

$
0
0

こんにちは、Yakumoチームの@ueokandeです。 本日はYakumoチームで取り組んだ、Relational Database Service (RDS)クラスタとElasticsearchクラスタをアップデートする仕組みを紹介します。

YakumoプロジェクトはUS市場向けにkintone.comをAWSから提供することをゴールにしたプロジェクトです。 プロジェクトではこれまで国内のデータセンターから提供していたkintone.comをAWSに移行します。 Yakumoプロジェクトの開発環境とデプロイパイプラインについては前回の記事をどうぞ!

blog.cybozu.io

背景

Yakumoで利用するAWSリソースは全てCloudFormationで管理しています。 CloudFormationは開発環境、ステージング環境、そして本番環境の構築や設定変更に利用しています。 これまではRDSやElasticsearchもCloudFormationで管理してました。 しかしCloudFormationで管理していると、クラスタのバージョンアップができないという課題がありました。

CloudFormationではRDSやElasticsearchのバージョンを指定できますが、すでに作成されたクラスタに対して異なるバージョンを適用すると、CloudFormationが既存のクラスタを一度削除します。 そのためRDSやElasticsearchのバージョンアップをしたくなったとき、今の仕組みだとお客様のデータをリストアする手順が必要になります。 そこでデータが失われること無く、RDSやElasticsearchをアップデートする仕組み作りに取り組みました。

理想のRDS・Elasticsearchクラスタの管理方法

まず仕組みを作る前にYakumoチーム内での要件を整理しました。 チームで実現したいことは以下の2つです。

  • バージョンアップや設定変更で既存のデータは消えない。
  • 既存のCloudFormationの定義と同様、利用してるリソースが宣言的(Declarative)に記述されてる。

1つ目の要件はもちろん外せませんが、開発チームとしては2つ目の要件も重要です。 近年Kubernetesを筆頭に、インフラを宣言的に記述できることが重要となってきています。 人が作りたいものをYAMLなどで記述して、システムがそれに基づいてリソースを作成したり、既にあるリソースを更新します。

Yakumoでも手順の自動化ではなく、インフラを宣言的に作成できるように、CloudFormationやKubernetesを導入してきました。 RDSやElasticsearchも同様に宣言的に管理して、所望のバージョンを指定するとそのバージョンにアップデートしたいというのがチームの要件でした。

AWS APIにはクラスタのアップデートAPIがあるので、クラスタを削除せずにアップデートする方法はいくつか考えられます。

  • クラスタ作成のみをCloudFormationで行い、アップデートや設定変更は手動オペレーションで実行
  • RDSやElasticsearchなどデータを持つリソースはCloudFormationで管理しない
  • CloudFormationのカスタムリソースでデプロイする

1番目、2番目は「宣言的に記述できる」という要件から外れるので採用しませんでした。 Yakumoでは3番目のカスタムリソースでデプロイする仕組みを作りました。

CloudFormationカスタムリソースによる管理

CloudFormationはカスタムリソースという独自のリソースを定義できます。 カスタムリソースはCloudFormation上で作成・更新・削除が開始すると、Simple Notification Service (SNS)に通知を送ったりLambda関数を実行できます。 そのためカスタムリソースを利用することで、標準リソースではできないユーザ独自のデプロイ処理やプロパティを定義できます。 YakumoではRDSやElasticsearchクラスタをカスタムリソースで定義して、Lambda関数内でクラスタを操作をするようにしました。

RDSクラスタのクラスタ管理の構成です。

カスタムリソースをデプロイする仕組み

CloudFormation上でカスタムリソースを作成・更新・削除すると、Lambda関数が呼ばれます。 Lambda関数ではAWS APIを呼び出してRDSクラスタを操作します。 更新時には現在のクラスタの状態と新しいプロパティ値を比較して、差分があるプロパティを既存のクラスタに適用します。

たとえば以下のようにカスタムリソースを更新してCloudFormationに適用すると、既存クラスタを削除すること無くRDSのバージョンアップが実行されます。

KintoneRDSCluster:
  Type: Custom::RdsCluster
  Properties:
    # 呼び出すLambdaのARNを指定する
    ServiceToken:
      Fn::ImportValue: !Sub "RdsDeployLambdaArn"

    # Lambdaにわたすパラメータ
    DBClusterParameterGroupName: !Ref RDSParameterGroup
-   EngineVersion: "5.7.mysql_aurora.2.04.3"+   EngineVersion: "5.7.mysql_aurora.2.04.4"
    ...

一見すると単純に見えますが、実際作ってみるといくつか注意点があったので順を追って説明します。

変更できないプロパティがある

一部のプロパティはAPIでは変更できません。 たとえばRDSはマスターユーザーのユーザー名を後から変更できません(CloudFormationの標準リソースではクラスタを再作成します)。 チームではこういったプロパティの変更はできないという制約条件として割り切りました。 Lambda関数でこれらのプロパティの変更を検知するとエラーを返すようにして、CloudFormationの適用を失敗させます。

こういったプロパティはよほどのことがない限り変更することはないので現状困りません。 仮にもし将来これらのプロパティの変更が必要になったら、Lambda関数を拡張するだろうということで、今は対応してません。

クラスタが利用できるまで待つ

CloudFormationで呼び出されたLambda関数は、クラスタを作成や設定変更が完了してクラスタが利用できるまで待つ必要があります。 もし待たずにすぐに処理を返すと、CloudFormationの適用が完了したけど実はまだ利用できないといったことが起こります。 また前回の適用が終わってないのに、新たに設定を変更しようとして失敗するといったケースも考えられます。

クラスタ作成や設定変更を待つには、GoのAWS SDKにあるrequest.Waiterを使いました。 このパッケージはAWS APIを一定期間実行して、所望の状態になるまで待つことができます。 ここでクラスタの作成・適用を待ってLambda関数の実行時間が長くなると、タイムアウトについても考慮する必要があります。

Lambdaのタイムアウトを考慮する

Lambda関数のタイムアウト上限値は15分ですが、この時間内にクラスタ作成や設定変更が完了しないことがあります。 Lambda関数が結果を返さなければ、CloudFormationはLambda関数をリトライします。 そのためLambda関数では多重実行を考慮して設計する必要があります。

例えば作成するリソースIDにランダムな文字列を付与したとします。 もしリソース作成時にLambda関数がタイムアウトすれば、1度目のLambda関数によって作成されたリソースを追跡できません。 そのためCloudFormationがリソースを削除できず残り続けます。

YakumoではRDSクラスタやElasticsearchの名前を、スタックに対して一意なIDにしました。 具体的にはスタックIDとリソースの論理IDのハッシュ値をサフィックスに付与します。 リトライ時にRDSクラスタやElasticsearchクラスタを特定できるので、Lambda関数で作成時にリトライされたか否かを特定できるようになります。 こういったベストプラクティスはAWS公式ブログでも紹介されてます。

まとめ

この記事ではYakumoプロジェクトでのRDSとElasticsearchクラスタの管理方法を紹介しました。 Yakumoプロジェクトは単なるAWS移行だけではなく、今まで国内のインフラでできなかったことや反省点を活かした、技術的チャレンジも多くあります。 その1つがこの宣言的にRDS、Elasticsearchクラスタを更新できる仕組みです。

短期的に見ると手動オペレーションでバージョンアップが手っ取り早いですが、長期的な観点で見ると宣言的なインターフェイスが昨今のインフラの理想でもあります。 Yakumoではまだ紹介しきれない知見や体験がありますが、それはまた別の記事で紹介したいと思います。

2018年 報奨金制度を振り返って

$
0
0

こんにちは。Cy-PSIRT(Cybozu Product Security Incident Response Team)の福永です。

本エントリでは

  • 2018年に実施した報奨金制度の結果
  • 参加者からのご意見

について、ご紹介いたします。

2018年に実施した報奨金制度の結果

定量情報

2018年の脆弱性認定数は155件、報奨金支払金額は21,055,000円でした。*1

着信数認定数(暫定)報奨金支払金額(暫定)
362件155件21,055,000 円

前年度と比較して、着信数、認定数ともに約1.5倍に増加し、報奨金支払金額の合計も倍近くに増えました。2017年7月7日から開始した「報奨金最大5倍キャンペーン」を、2018年には、通年化したことが主な要因です。 2015年から2018年の着信数、認定数、報奨金支払い金額

2018年度報奨金獲得ランキング

総額ランキング

獲得した報奨金額の合計が最も多かったのは、西谷完太(@no1zy_sec)様でした。他にもたくさんの方からご報告をいただいています。皆様ありがとうございました。

順位掲載名
1位 西谷完太(@no1zy_sec)様
2位東内裕二(@yousukezan)様
3位米山 俊嗣(三井物産セキュアディレクション) 様
4位Masato Kinugawa 様
5位馬場 将次(株式会社神戸デジタル・ラボ)様


脆弱性1件あたりの高額ランキング

報告いただいた脆弱性1件あたりの報奨金額が高額だった方のランキングです。馬場 将次(株式会社神戸デジタル・ラボ)様に報告いただいた脆弱性が1位でした。おめでとうございます。

順位掲載名
1位 馬場 将次(株式会社神戸デジタル・ラボ)様
2位米山 俊嗣(三井物産セキュアディレクション) 様
3位Masato Kinugawa 様
4位東内裕二(@yousukezan)様
5位東内裕二(@yousukezan)様

2018年のトレンド

1位(XSS)と2位(不適切な入力確認)で認定数の半分を占めています。2016年の1位、2017年の2位「XSS」が2018年の1位に返り咲きました。例年と比べると、「CSRF」と「パス・トラバーサル」の報告が多いという結果でした。

認定された脆弱性(脆弱性タイプ別)

また、2018年の傾向として「海外の方からのご報告が多くなった」という点が欠かせません。 各種ドキュメントの英語化を進めている影響が直に出ているのか、全体の着信数362件のうち11パーセントにあたる41件が英語でのご報告でした。 前年度は、全体の着信数228件のうち3パーセントにあたる7件でしたので、前年度と比較して、割合でみると8ポイント増です。

今後とも、多くの方にご参加いただけると嬉しいです。

参加者からのご意見

弊社の報奨金制度をより良くしていくために、 制度に参加いただいたバグハンターの方にアンケートを実施しました。皆様よりお寄せいただきましたご意見を一部、ご紹介します。

  • 評価にブレがある

評価基準が途中で変更になることもあり、過去の評価とは必ずしも一致しません。新しい攻撃手法が明らかになるなかで、評価基準については、日々社内で検討を重ねているためです。何卒ご容赦ください。

  • 謎ルールが存在する

参加者の皆様にとって分かりやすい制度となることを目指して、また、参加者間でルールについての認知に差が出ないようにするため、可能な範囲で、報奨金制度ルールブックや、脆弱性認定ガイドラインでの公表を進めてまいります。

  • バグハン合宿に参加していないとリアルタイムランキングに不利

2019年度は、制度開始と同時にバグハン合宿があったことで、合宿への参加がリアルタイムランキングに有利に働くのでは、というご意見です。

こちらに関しては、前年度の制度終了(通常12月後半)以降に着信したご報告も、翌年度の集計対象に含みますので、ぜひ、制度開始を待たずに、ご報告いただければと思います。

  • 再評価のプロセスを教えてほしい

次のケースにおいて、再評価を実施しました。基本的には再評価は行っていません。

  • 他の脆弱性情報を起点に社内で議論に上がり、以前の報告についても評価を見直したケース
  • 情報公開対応を進める過程で、外部機関より評価について指摘が入り見直したケース


ありがたいお言葉も頂戴いたしました。

  • 評価大変かと思いますが、頑張って下さい!
  • The way you communicate and provide quick response is fabulous. Really loved the work you guys passionately do. Keep doing and making much secure Cybozu.

2019年の取り組み

脆弱性報奨金制度2019 はじまります - Cybozu Inside Out | サイボウズエンジニアのブログでご紹介しています。

さいごに

サイボウズでは、2019年も継続して報奨金制度を運営しています。

バグハンターの方にとって、魅力的な制度であり続けたいという思いで日々対応しておりますので、「サイボウズにこのような企画や施策を実施してほしい」というご要望がありましたら、 メールで productsecurity@cybozu.co.jp までお気軽にご連絡いただければ幸いです。

今後とも、よろしくお願いいたします。

*1:2019年6月現在の暫定値であるため、評価の変動により増減する可能性があります。最終結果は、確定次第アップデートいたします。

Viewing all 681 articles
Browse latest View live