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

KubeCon + CloudNativeCon China 2019 現地レポート 0日目

$
0
0

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

KubeCon + CloudNativeCon Europe 2019が終わったばかりでしたが、約1ヶ月後に次のKubeCon開催がやってきました。本イベントはOpen Source Summit Chinaと同時開催で、Linusを始めとしたLinux kernel開発者たちのキーノートセッションも見どころです。

f:id:cybozuinsideout:20190625052524j:plain

KubeCon + CloudNativeCon ChinaはEuropeやNorth Americaと異なり、変則的なスケジュールとなっています。今まで0日目としていたCo-located event(コミュニティや企業主催のイベント)だけでなく、同日午後にKeynoteが開催されました。

それではCo-located eventの一つ「Kubernetes Contributor Summit in Shanghai」を元に紹介された情報や、印象に残った発表を簡単に紹介していきます。

Kubernetes Contributor Summit in Shanghai

Kubernetes Contributor SummitはKubernetesを中心に貢献している人や、これから貢献する人に向けたmini summitです。SIG(Special Interest Group。各機能の分科会)による次回のKubernetesリリースに向けた計画などを議論しています。

f:id:cybozuinsideout:20190625052629j:plain

スケジュールはこちらです

community/events/2019/06-contributor-summit at master · kubernetes/community · GitHub

起草 KEP, Drafting A KEP - Kevin Wang, Leon Wang, Fabio Rapposelli

Kubernetes Enhancement Proposal(以下KEP)の作り方について紹介しています。KEPは新しい機能や潜在的な問題定義に対して新たな提案を行うための「プロセス」です。これはいきなり新機能実装の大きなPRなどコミュニティが困惑しないような形で、計画や考えをまとめる機会の提供でもあります。可能な限り計画をまとめ、ユーザーストーリーやその機能実装によるメリット・デメリット、新機能であればAlpha -> Beta -> GAなどそれぞれのマイルストーンで目指すゴールを記述する必要があります。

現在のKEPはこちらにまとまっています https://github.com/kubernetes/enhancements

次のKubernetesリリースにどのような機能が追加されるかは上記のリポジトリを追うことで、今後の動向を追うことができるでしょう。

f:id:cybozuinsideout:20190625052847j:plain

CodeBase 之旅, CodeBase Tour - Fabio Rapposelli

github.com/kubernetes organizationをはじめとしたリポジトリ構成とコードの追い方について解説しました。まずGitHub organizationは以下があります。

続いて https://github.com/kubernetes/kubernetesについてです。こちらがUpstreamのKubernetesのソースコードであり、Organizationとリポジトリ名が"kubernetes"であることから「KK」と呼ばれることがあります。それぞれ以下のディレクトリツリーについて触れていました。

  • pkg/: Business logic。外部からインポート可能な実装だがまだ完全に実装が終了していないもの。GA(General Availability)として予定された機能が完成したら、後述のstaging/ツリーに移行される。
  • pkg/kubectl: kubectlコマンドのBusiness logic。
  • staging/: 実装完了した機能のツリー。これらはk8s.io/foobarといったpathでインポート可能であり、GitHub上ではgithub.com/kubernetes/foobarという名前で定期的にレプリケーションされている。つまりstaging/以下の実装について修正したい場合は、こちらでPull Requestを作成する。
  • cmd/xxx: GitHub release上でアップロードされるコマンド群。

pkg/以下やstaging/以下は、管理不足な箇所もあり github.com/kubernetes/foobar といった別のリポジトリで運用されているものがまだあるようです。さらに本体にはまだ取り込まれていないGCPやAWSなどのクラウドプロバイダー自身等がメンテナンスしているリポジトリもあります(e.g. https://github.com/digitalocean/digitalocean-cloud-controller-manager)。多くの参照先があるので、特定の機能における最新コードを追うためのヒントを提供しました。

Keynotes

Keynote: CRDs, No Longer 2nd Class Thing! - Jing Xu, Software Engineer, Google & Xing Yang, Lead Architect, OpenSDS

前述で触れたKubernetes向けCSIベースの機能開発をリードしている2人によるCRD(Custom Resource Definition)についての紹介です。CRDはKubernetes APIを拡張するためのもので標準にないリソースを自分で好きな定義で提供可能にします。今日のOperator実装ではCRDを提供し、独自で用意したリソースを使ってデプロイ可能にすることが一般的となっています。例えば彼女らはPersistent VolumeのSnapshot機能について開発してKubernetes本体に取り込むような活動を続けていましたが、必須ではないためSIG ArchitectureによってRejectされました。Optionの扱いなものはCRDとして提供してもらい、必要不可欠なリソースのみKubernetes本体に取り込むというコミュニティの方針があるようです。Kubernetesプロジェクトは開始から5年が過ぎ、爆発的な速さでコードが増えています。過去の参加レポートでも少し触れましたが拡張性を目指した動きになるため、今後はKubernetes本体のリソースではなくCRDやそれを取り扱うOperatorの実装という形が主流になるでしょう。

f:id:cybozuinsideout:20190625053214j:plain

この機能は現在、VolumeSnapshotというカスタムリソースで利用可能となっています。

続いてPandaというCRDを提供し、QiQi(七七, チーチー)という上海動物園で最近生まれた赤ちゃんパンダを例にmanifestを定義するというデモを紹介しました。これはCRDについてよく知らない参加者向けに分かりやすかったのではと感じました。

f:id:cybozuinsideout:20190625054021j:plain

まとめ

KubeCon ChinaのContributor Summitはどちらかというと、これから実際にソースコードを触る人たちの参加者が多く、次のリリースに向けた議論は同時に行われたSIGメンバーの中で行われていました。一日目のキーノートにおいて参加者数が発表されるでしょうが、中国開催は北米、ヨーロッパ開催よりも参加者が少なく、セッションレベルを下げているところも見かけます。はじめての人達に知ってもらう目的もあるかもしれませんし、先月のヨーロッパ開催でほとんど議論が行われていたためかと推測します。また海外の参加者にとっては、いきなり中国語セッションに変わったりして困惑していた人もいました。一方で「全セッション英中同時通訳」など現地の人にとっては国際会議の中では入り込みやすいイベントではあります。

引き続きKubeCon Chinaの参加レポートを投稿しますのでお楽しみに!


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

$
0
0

こんにちは、Necoプロジェクトの井上です。 昨日に引き続きKubeConの現地レポートをしていきます。本日は KubeCon + CloudNativeCon China 2019 の1日目ということで、多くのセッションが同時並行で開催されていました。中国語のセッションも多かったですが、会場には翻訳者が各セッションに配置されていたため、中国語がわからなくても参加が可能でした。 また、キーノートでは Linuxの生みの親であるLinus Torvalds氏 が登壇しました。

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

blog.cybozu.io

本記事では参加したセッションで紹介された情報や、印象に残った発表を簡単に紹介していきます。

Keynote: Linus Torvalds, Creator of Linux & Git, in conversation with Dirk Hohndel, VP & Chief Open Source Officer, VMware

Linus氏と Dirk氏 が対談する形でのキーノートです。 Linus氏が登場すると会場全体のテンションが一気に上がったのが伝わってきました。 いろいろ話していたのですが、印象に残っていたのは、Linus氏が Kubernetes について OSS の先輩としてアドバイスを求められた時、Kubernetes は若いプロジェクトなので、文化とコミュニティを作るのが大事だと話していたことです。 また、Linux の未来に関して、進化してきたのはハードウェアそれ自体とどう使うかであって、Kernel 自体は将来のハードウェアがどうなるかはわからないけれども、Kernel は変わらないだろうと述べていました。 なぜなら50-60年前からリソースマネージャとハードウェア, ソフトウェアとのインターフェースの役割を果たすのは変わっていないからです。Kernel developer でない私にとっては、Kernel が将来的にどのようになっていくのかということに関して、あまりイメージがなかったため、Linus氏の発言は新鮮でした。

f:id:cybozuinsideout:20190626052411j:plain
Linus Torvalds, Creator of Linux & Git, in conversation with Dirk Hohndel

How Should You Effectively Use etcd Metrics - Wenjia Zhang & Jingyi Hu

このセッションは中国語で行われ、主に etcdのメトリクスについての話をしていました。 etcdとはKey-Value型のデータベースで、Kubernetesではクラスター情報を保持するために使われています。 セッションでは etcdのプレフィックスがetcd_server_etcd_network_のメトリクスに関する説明と、新しくメトリクスとして追加された、snapshot metricspeers healthines, strage layer metrics等を紹介していました。 また、etcd クラスタを構成するサーバの形として learnerが 紹介されました。 learnerは etcd で採用されている Raft と呼ばれる分散合意 ( Consensus ) アルゴリズムにおいて、voting を持ちません。なので、新しいサーバを追加する際に learnerとして追加することで、Raft が不安定にならず、クラスタに安全に追加することができます。機能としては今年の5月に入りました。

f:id:cybozuinsideout:20190626052809j:plain
How Should You Effectively Use etcd Metrics - Wenjia Zhang & Jingyi Hu

E2E Testing: Real Developers Don't Test... But They Should

End to end テストをもっとすべきだというセッションでした。セッションでは、NFS driver, pmem-csiを例にして ginkgo, gomegaを使ったやり方の説明を行なっていました。また、テストにおけるパラメータの使い方や、viperの説明なども軽く触れられていました。ここら辺のツールは我々の Neco プロジェクトでもよく使用しており、すんなり理解できました。 最後の質疑応答では freezing test (リリース直前にテストを安定させるため、テストの変更をしない期間) に関する補足も入りました。

f:id:cybozuinsideout:20190626053148j:plain
E2E Testing: Real Developers Don't Test... But They Should

まとめ

KubeCon China では、関心がある分野の Session も多く、大変勉強になった他、普段会えないような人に質問したり、他の参加者と仲良くなったりと、とても有意義でした。また、Session の後、speakerに質問することが多かったですが、皆さん、丁寧に説明してくださり、とてもありがたかったです。例えば、E2E Testing の Session で Neco プロジェクトで最近問題になったE2Eテストが長引く問題をどう解決すべきかに関して、質問したところ、mtest や code review を徹底することで、E2Eテストで実施が必要なものだけを残してテスト時間を減らすのはどうかというアドバイスを頂きました。

KubeCon + CloudNativeCon China 2019 の最終日のレポートも引き続きお送りいたしますので、お楽しみください!

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

$
0
0

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

本記事はKubeCon + CloudNativeCon China 2019(以下本イベントと記載)の最終日であるday2の参加レポートです。

day0とday1の記事はこちらです。

blog.cybozu.io

blog.cybozu.io

筆者はday2ではどちらかというとKubernetesそのものよりも、その土台となるソフトウェアを扱うセッションに参加することが多かったです。本記事では筆者が参加した2つのセッションで触れられたソフトウェアそのもの、および、それらがKubernetesにどのようにかかわっているかという観点で紹介します。

Secure Container with SGX: Protecting Secret in Cloud Environment - Isaku Yamahata, Intel & Xiaoning Li, Alibaba

これはアプリケーション*1のセキュリティ機能を高めるためのGraphene-SGX(以下本ソフト)というソフトウェア、およびそれをGo言語で作られたアプリケーションに適用できるように拡張した取り組みについての話でした。もともとは本節のタイトルにあるようにIsaku YamahataさんとLiさんの2人による発表の予定でしたが、Xiaoning Liさんが都合により参加できなくなったため、Yamahataさん一人による発表となりました。

まずは本ソフトのもとになったGrapheneというソフトの説明からはじまりました。本ソフトは一言でいうと、C言語で書かれた既存のソフトウェアを、リコンパイルすることなくセキュリティを向上させられるライブラリです。ソフトウェアはOSカーネルに処理を依頼するためのシステムコールを発行するときにCPUの特殊な命令を使います。C言語で書かれたソフトウェアにおいて、システムコールは通常libcと呼ばれるライブラリが持つシステムコールのラッパー関数を介して呼ばれます。本ソフトはこのラッパー関数を、システムコールを直接呼び出すだけではなくセキュリティをより高めるための処理を追加した関数で置き換えるというライブラリです。アプリケーションの実行時にリンクされるlibcをGrapheneで置き換えるため、アプリケーションの変更は不要、というわけです*2

続いてGprahine-SGXについての紹介がありました。Software Guard Extention(SGX)とは、簡単に説明するとアプリケーションを実行する基盤となるカーネルや仮想マシンに脆弱性があったとしてもアプリケーションのデータを盗み見られないようにするための、IntelのCPUに搭載されている機能です。SGXを使うには通常アプリケーションのソースコードを書き換える必要があるため、現在世の中に普及しているとはいいがたい状況でした。本イベントのday1においてもLinuxの主要開発者であるGreg KHさんが「SGXは誰も使っていない」と言ったことについてYamahataさんが本セッション中に嘆いていました。

この状況を打破するきっかけになりうるのがGraphene-SGXです。本ソフトはSGXを使えるようにGrapheneを拡張したものです。アプリケーションをGraphene-SGXを介して実行することによって、もともとGrapheneが持っている機能に加えてSGXによってもアプリケーションのセキュリティ機能を高められる、かつ、アプリケーションの実行ファイルを変更する必要もない、というわけです。

最後にYamahataさんによる本ソフトの機能拡張について紹介がありました。GrapheneおよびGraphene-SGXには一つ弱点があります。それはlibcを使わないアプリケーションには使えないということです。その代表的なものが現在DockerやKubernetesなどの有名どこを含めてたくさんのソフトウェアで使われているGo言語で書かれたプログラムです。この課題を解決するために彼はGo言語の実行ファイルを実行時に書き換えてGraphene-SGXの機能を呼び出すようにするという方法を使いました。C言語で書かれたプログラムとGo言語で書かれたプログラムは構造が大きくことなるために大変な苦労をされたようですが、ひとまず簡単なアプリケーションが動く状態にはこぎつけたようです。

Yamahataさんが拡張した機能は現在のところ本家Gprahine-SGXとは別のプロジェクトですが、現在本家へのマージを試みているところだそうです。あるOSSをforkして新しい機能を追加したら積極的に本家に還元するというOSSのエコシステムにおける非常によい流れだと思いました。

本ソフトがKubernetesとどのように関係するかというと、Pod内で動かすアプリケーションを本ソフトを介して実行することによって、アプリケーションの実行ファイルを変更せずに他のプログラムからのデータの盗み見を回避できるようになるということです。将来的にはKubenetesそのもの、およびKubernetes上で動作するアプリケーションにおいてよく使われる機能になるかもしれません。

余談ですが、筆者とYamahataさんはお互いにLinuxカーネルの開発に携わっていた頃から数えて10年来の知り合いです。お互いに連絡を取り合わずに現地で偶然再会したので非常に驚きました。現在何をしているかなどについて話に花を咲かせたのに加えて、day0の記事を書いたmitzさんとの新たなつながりができるなど、充実した時間を過ごしました。このように旧交をあたためたり新しいつながりができたりするのもこの手のイベントの魅力です。

New Cgroup Subsystem for Buffer Write io and Network RX Control in Kernel - Dongdong Chen, Tencent

これはLinuxカーネルのリソース制御機能であるcgroupsというカーネルの機能のうち、ストレージI/OおよびネットワークI/Oの帯域を制御する機能について紹介するセッションでした。

まずcgroupsについて簡単に説明しておきます。これはCPUやメモリ、ストレージI/O、ネットワークI/Oなどのリソースをプロセスやそのグループ(以下プロセス群と記載)ごとに利用制限をかけるためのカーネルの機能です。たとえばメモリのリソースを管理するmemory cgroupという機能は、プロセス群が使用できるメモリ量に制限をかけられます。たとえばユーザAとユーザBが同じ料金を払って使用しているシステムにおいて、ユーザAのアプリケーションがメモリをほとんど使っているためにユーザBのアプリケーションが起動できなくなる、といった事態がmemory cgroupによって避けられます。cgroupsはとくに仮想マシンやKubernetesなどのコンテナ実行環境において広く使われています。Kubernetesにおいては以下のようにcgroupsを活用してコンテナのリソース制御をしています。

Managing Compute Resources for Containers - Kubernetes

1つ目に紹介されたのはプロセス群ごとにストレージI/Oの帯域を制限する機能です。たとえばプロセス群Aは100MB/s、プロセス群Bは50MB/sにするといった使い方をします。この機能の主な目的は特定のプロセス群が大量のストレージI/Oを発行することによって他のプロセス群のI/Oがなかなか終わらないという事態を避けるためのものです。従来はこの機能は通常のページキャッシュ*3を使わないdirect I/Oと呼ばれる特殊なI/Oに対してしか効果がなく、使いづらいものでした。Chenさんが紹介したのはページキャッシュを使う通常のI/Oにおいてもこの帯域制御ができるという機能です。彼は実際に機能が動作している様子をグラフを使って説明するとともに、この機能がどのように実装されているかについて説明してくれました。実装についての説明が非常に興味深かったことに加えて、機能の有効性についてわかりやすく視覚的に理解できたので非常に有益でした。

2つ目に紹介されたのはプロセス群ごとにネットワークI/Oの帯域を制限する機能です。彼はネットワークのリソース制御のうち、受信量の制御について説明してくれました。これは特定のプロセス群のネットワークを介したデータ受信量を、たとえばプロセス群Aは100MB、プロセス群Bは50MB/sのように制限する機能です。この機能のおかげで特定プロセス群がネットワークI/Oの帯域を占有することによって他のプロセス群のネットワークI/Oが阻害されるのを防げます。こちらについても実装と動作例について図を使った紹介がありました。

普段Kubernetesを使っていてcgroupsを直接意識することは少ないとは思います。しかし、いざKubernetesのリソース制御機能にまつわるトラブルが発生したときにはcgroupsについての知識が無いと何が起こっているか見当がつかないために調査が非常に難航します。Kubernetesを使ったシステムをただ動かすのではなく長期間運用していくためにはcgroupsは知っておいて損はない機能です。

おわりに

最後に本イベントの全体としての感想を述べておきます。まず、今回はOpen Source Summitと共催になったことによって、アプリケーションからlinuxカーネルまでの幅広い知識を一気に得られる良いイベントになったのではないかと思います。というのも、KubeConではKubernetesそのものについての知識は得られるのですが、Kubernetesが内部的に使っているLinuxカーネルの機能についてはOpen Source Summitに参加するなどの方法で別途得なければいけなかったからです。Necoプロジェクトにおいてはソフトウェアスタックをカーネルの領域まで下りて調査することも珍しくないので、今回の共催は大いに助かりました。

続いて規模について。年に三回開催されるうち、本イベントは開催期間が一日短く、かつ、規模も半分程度です。これについては大きければよいというものではなくて、本イベントくらいの規模が適切だと思いました。なぜかというと最近参加したKubeCon North AmericaやKubeCon Europeは参加人数が一万人に近づいたりと盛り上がってはいるのですが、セッションが最高20並列で進んだり、部屋間の移動で大渋滞が起きたりと一か所で開催するのに無理があると筆者は考えています。

最後に会場の雰囲気について。わたしがこれまで参加してきた大規模な国際なイベントはすべて日本、北米、ないしヨーロッパで開催されていたものであり、そこではすべてのプレゼンおよびそれに使う資料は英語で統一されていました。しかし本イベントはそれとはまったく異なり、中国語で発表されるセッション、およびプレゼン資料が中国語というものがかなりありました。では中国語を介さないわたしのような人にはまったくわからないかというと、全セッションにおいて中→英、英→中の同時通訳があるためにある程度理解できるというようになっていました。「国際」といってもいろいろなやりかたがあるのだなと感心しました*4

来年も本イベントへの参加が叶えば、また読者のみなさまにこのような形で情報を共有できればと思います。ここまで読んでいただき、ありがとうございました。

*1:正確には任意のプログラムが対象ですが、ここではアプリケーションと記載します

*2:このようなソフトウェアに興味のあるかたは"Library OS"というキーワードで検索してみてください

*3:アプリケーションがデータを読み書きするときに毎回ストレージデバイスにI/Oを発行するのではなくデータをメモリ上にキャッシュすることによってアプリケーションから見たI/Oの高速化を図るカーネルの機能

*4:day0では中国語のスライドで中国語の発表をして同時通訳なしというセッションがいくつかあったのでほとんど何もわからずにかなりつらかったですが…

ヘルプサイトについて語るMeetupやりました

$
0
0

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

すっかり梅雨ですね。皆さんMeetupしてますか?(梅雨関係ない)
うちのチームは初めてやりました。今回はその開催報告です。

参加者20人で開催予定でしたが、募集してみると、ありがたいことに3倍近い参加希望をいただく結果に。急遽参加者枠を30人に増やして開催させていただきました。参加いただいた皆様、ありがとうございました!

今回のテーマ

テクニカルコミュニケーションチームは、開発部の中でライティングや翻訳をメインで担うチームです。プロダクトのUIに出す言葉のライティング、翻訳(ローカライズ)、ヘルプサイトの運用などの仕事をしています。

今回のMeetupは「ヘルプサイトについて語る」をテーマにしてみました。

cybozu.connpass.com

ヘルプって何気にかなりのアクセスがあります(サイボウズのヘルプだと、オンラインのものだけでも月70万~80万PV程度)。ですので、顧客との大きな接点の1つです。プロダクトのUXの一環として、いいヘルプにしなければと考えています。

会場準備の様子
会場準備の様子

セッション1 「ヘルプサイトの制作プロセス」

最初にイントロダクションとして、私からヘルプサイトの制作プロセスの話をしました。

speakerdeck.com

普段ヘルプサイトのライティングやディレクションをしていて、「誰に何を伝えるか」「どのような構成にすると情報を探しやすいか」を考える上流工程が大事だと感じます。 そこがボンヤリとしていると、記事ができてきた頃になって「もっと初心者向けに書かないと伝わらないんじゃない?」「この情報はこの記事に書くべきことじゃないよ」など意見が出て、書き直しに近い状態になってしまいがちです。

そんな背景から、上流工程で決めておくべきことと、その際に考慮するポイントについてお話しました。

セッション2 「ヘルプサイトのデータ分析」

次のセッションは、ヘルプサイトのデータ分析について。

www.slideshare.net

使いやすく役に立つヘルプにするためには、公開後の継続的な改善が重要です。ユーザーが必要な情報に辿り着けているか、載せる情報に不足はないかなど、問題を見つけて次の改善へと繋げていきます。ヘルプの運用にかけられるコストは限られているでしょう。限られたコストでヘルプを効果的に改善するには、どこに手をつければ最も高い効果が得られるかを確認しながら進めていかなければなりません。

ということで、他のWebサイトと同様に、ヘルプサイトでもデータ分析が大切です。このセッションでは、定量分析と定性分析を組み合わせた改善例、開発チームへのレポーティングについて取り上げました。

セッション3 「GitHubと翻訳支援ツールで多言語サイトの更新を超速にする」

最後のセッションは、ヘルプサイトの多言語対応について。

www.slideshare.net

日本語だけでヘルプサイトを運用していた頃は、改善点を見つけると、気軽に即時反映していました。しかしながら、サイトを多言語化し始めると、言語ごとのコンテンツを同期させるのが大変で、更新が重荷になってきていました。多言語サイトを運用している方には、あるあるな悩みだと思います。

そんな中で、開発のアジャイル化が進み、プロダクトのリリースサイクルは短くなる一方。これは仕組みを一から見直さないと更新が追いつかないぞ! ということで、このセッションでは多言語なヘルプサイトを高速に更新していくための仕組みを取り上げました。 ヘルプサイトだけでなく、多言語対応するドキュメント系サイト全般に応用できる仕組みだと思います。

最後に

3セッションのあとはご参加いただいた方々との交流会。大手企業でヘルプサイトやFAQを運用している方、スタートアップでヘルプサイトを作り始めようとしている方、翻訳に携わっている方など、たくさんの方々と情報交換できました。

ブログを書いてくれた方もいました。この場を借りてお礼申し上げます。ありがとうございました!
http://awwa500.blogspot.com/2019/06/cybozu-meetup-19.html

今回は初開催でしたが、予想以上に多くの人に興味を持ってもらえることがわかりましたので、テーマを替えながら今後もやっていきたいなと思いました。開催の際は是非ご参加ください!

最後にお知らせ。現在弊社ではテクニカルコミュニケーターとテクニカルライターの募集を出しています。ご興味がある方は是非ご応募ください!
https://cybozu.co.jp/company/job/recruitment/list/technical_writer_kintone.html
https://cybozu.co.jp/company/job/recruitment/list/tc_communicator.html

※1つめの資料だけ大きく表示されていますが、自分のを目立たせようとかそんな意図はありません。。SpeakerDeckとSlideShareの仕様の違いと思われます。

「サイボウズ バグハン合宿 2019」開催報告

$
0
0

こんにちは!!! (合宿は最後にするぞ)と準備をしながら思っていたのに、終わった瞬間から次の合宿のことを考えてしまっていた PSIRT の大塚(由)です。 2019/4/20,21に、2年ぶり3度目となるバグハン合宿を開催しましたので、当日の様子と結果についてご報告いたします。

バグハン合宿とは

「サイボウズ バグハンター合宿」、略して「バグハン合宿」。 バグハンターの方々をオフラインで集め、脆弱性をワイワイ見つけてもらうというイベントです。あらかじめこちらで決めたチームで戦っていただきます。運営がその場で脆弱性の評価をし、評価結果に基づいたチーム得点で競い合うというものです。 今回は、12名のハンターの方々にご参加いただき、1チーム4名(ハンター3名+弊社開発者1名) の4チームで戦っていただきました。

タイムスケジュール看板の写真
XX:XX の時間が長かった...。

1日目

戦いの場所は、湯河原。天然温泉のお宿です。合宿初日に、2019年度の報奨金制度をスタートした関係もあり、まずは変更点について簡単な説明を。説明の後、いよいよバグハンスタート!!「"宿題"は禁止されていなかったので」というルールの穴を突いていただき、事前に見つけた脆弱性を粛々と報告するハンターの姿もチラホラ・・。スロースタートかと思いきや、いきなり大忙しの初日となりました。

特別仕様のパイの実の写真看板の写真
特別仕様のパイの実。ストラップで、撮影やコメント可否の意思表示を出来るよう配慮

夕食&食後の戦い

合宿の目的の一つに、運営との交流・ハンター同士の交流ということも掲げていたため、宴会場を貸し切り温泉宿らしいお食事をみんなでいただきました。大吟醸を飲まれる強者もいる中で、ほとんどのハンターがお茶を選択され、夜に向けた意気込みが感じられました。

夕食がひと段落すると、"エクストリームス"という数字ゲームをしました。こちらもチーム得点に換算しました。ある条件下で大幅減点という弊社独自ルールを加えた特別バージョンです。単純なゲームなのですが、運あり戦略ありで、ワイワイと頭休みを楽しみました。

深夜

今回、24時間会議室を使うことが出来てしまいました。ほとんどのハンターが、睡眠よりもバグハンを優先されていました。温泉に入って頭を切り替えたり、時にはハンター同士で議論されていたり、合宿ならではの楽しみ方がギュッと凝縮されていた時間帯だったように思えます。

議論している写真
合宿を通して、ハンター同士が脆弱性や評価に対して議論している姿が多く見られました

2日目

11時で、バグハンタイム終了!予定では11:30まででしたが、あまりの登録数に評価が追い付かないと判断し、急遽30分繰り上げました。皆様、前日の疲れも感じさせない怒涛の報告数となりました。
終了後は近くのお蕎麦屋さんで、見つけた脆弱性や評価に対するあれやこれやなど、お互いの労をねぎらいながらの昼食をとりました。

そばの写真
終わりの見えない評価数に怯え、おそばの味をほとんど覚えていません。

結果発表

  • 優勝チーム
    優勝は、Cチーム!!報告数・認定数共に2位でしたが、全員が複数件報告し、どなたもCVSS評価の平均値が高く見事優勝となりました。

    優勝チームの写真
    横田智哉(弊社開発者)、ren_hxさん、tyageさん、yoneyoneyoさん

  • 本部長賞
    mage さん!!暗号化されているソースコードをPHPの中間コードを解析することでロジックを読み解き、報告されていました。本部長に「次はもうソースコードも開示しちゃおうか・・」と言わせてしまうほどの高い技術力で、満場一致での選出となりました。

    本部長賞の写真
    mageさんと開発本部長の佐藤鉄平

  • PSIRT賞
    mage さん!!脆弱性の改修漏れを報告いただきました。この合宿直後に改修済として公開予定だったため、冷や汗ものでした。本部長賞と同一人物であるため議論にはなったのですが、それでもやっぱり脆弱性情報の公開作業なども行うPSIRTとしては、この報告が一番印象深いということで選出させていただきました。

結果詳細

報告数:198件、認定数:155件 という結果となりました。(合宿終了時点での数値)
以下は、合宿で検出されたCWEタイプの傾向です。色々な観点で多くの報告をいただきました。
CWEタイプの傾向の円グラフ

現在、合宿時の評価をクリアにし、全ての報告を評価し直しております。少々お時間いただけますと幸いです。

合宿を終えて

  • とても勉強になる&楽しい
    普段ハンターの方々とメールでコミュニケーションをしておりますが、直接話をしたり画面を見ながら確認することでいつもよりスムーズな議論が出来たと思います。また、最後に各チームの代表者に見つけた脆弱性や手法についてLTしていただきました。どなたも尖った内容でとても勉強になりました。意見が対立する場合は直接議論が精神的に辛いケースもありますが、それらも含めて合宿ならではの刺激となり、やっぱり楽しい!と思いました。
合宿の様子1合宿の様子2
  • 予想を上回る報告数
    ハンターの方々の本気により、たくさんの脆弱性を報告していただきました。各場面で交流も多く見られ、ハンター同士のふとした会話や知見の共有から派生した報告もあり、チーム戦にしたことや合宿という場であることのメリットを感じた結果となりました。 合宿の様子3

  • 複数視点での指摘
    12名のハンターが参加され、198件が報告されたという状況の中で、重複したものが僅かという結果でした。限られた製品・限られた人数で攻撃していただいたにも関わらず、それぞれ検証される観点が違ったところが大変興味深かったです。同時に、多くの方に見ていただく意味という点で報奨金制度を運営するメリットを実感しました。

反省点

  • connpass での募集トラブル
    普段、報奨金制度に参加している方を優先的に扱うことや、ランキング上位者を招待することが決まっていました。にもかかわらず、connpass(抽選)で応募をかけたこと、不備があり発表当日に混乱を招いてしまったこと、この場をお借りし改めてお詫び申し上げます。今後は、connpass(先着順or抽選)の利用ケースに該当しない場合は、専用フォームを用意して募集をかけるようにいたします。

  • 合宿の評価数
    想定外の報告数により、運営側が1件の評価に割ける時間がとても少なかったです。その結果、既知のチェックが間に合わなかったものもありました。もし次回やるとするならば、合宿時の評価フローや進め方を見直す必要があると感じています。

攻撃を知ることは、防御につながる

弊社開発者もチームの一員として戦うことで、普段自分たちが開発・運用している製品に対してハンターと一緒に攻撃をしました。ハンターの方々との会話や報告内容から、多くの攻撃観点を学ぶことが出来たと興奮気味に語っていました。守るためには、攻撃手法を知ることが大切だと考えます。これからもハンターの方々からの生きた攻撃を学ぶことで、PSIRTだけではなく開発や運用とも協力しながら、セキュアな製品作りに取り組んでいきたいと思います。

最後に

今回は、土日にも関わらず多くの方にご応募&ご参加いただきありがとうございました。合宿はクローズドな場であるとは言え、多くの非公開情報をオープンにやり取りをしました。合宿だけではなく報奨金制度自体が、ハンターの方々との信頼関係あってこそ成り立つものだと思います。

世界中に多くの BugBounty が存在する中で、弊社の製品に費やしていただく全ての時間に、とても感謝しております。皆様からのご意見ご要望を真摯に受け止め、今後の運営に生かしてまいります。
次回、(そう遠くない時期に...) 更にパワーアップした合宿でお会いしましょう!!
引き続き、サイボウズ報奨金制度をどうぞよろしくお願いいたします。

集合写真
ありがとうございました!たのしかったです!(合宿は終わったが、評価はつづく....)

お手軽Kubernetesクラスタ作成ツール "kind"の紹介

$
0
0

こんにちは、Necoプロジェクトのsatです。今回はKubernetesクラスタお手軽に作れるkindというツールを紹介します。kindはKubernetes In Dockerの略です。

本記事の要約

  • kindとはKubernetesクラスタを簡単に作れるツール
  • インストール、およびクラスタ作成がそれぞれたった1コマンドを実行するだけで完了
  • 他の類似ツールには存在しないマルチノードクラスタ作成機能がある
  • Kubernetesの公式プロジェクトかつ、Kubernetesそのもののテストにも用いられているため、実績や将来性は十分

kindとは

Kubernetesは複雑なソフトウェアなので、自分で一から構築するのは非常に大変であり*1。 このため、世の中にはローカル環境でとりあえずKubernetesを使ってみたいというような用途でminikubemicrok8sといった様々なツールが存在しており、kindもその一つです。 それぞれ構築できるKubernetesクラスタがサポートする機能が異なるために一長一短であり、どれを使うべきかは使いたい機能や好みによって変わります。 ただしkindにはマルチノードクラスタを作れるという他のツールには無い特長があります。

kindはKubernetesの公式ツールであり、v1.11以降ではKubernetesそのものをテストするために使われています。このためkindは実績と将来性が優れているといえます。

kindはKubernetesやその周辺ツールにまつわる問題についてそれぞれのコミュニティにissueを発行するときにも有用です。なぜならば独自ツール *2などの世間的には マイナーな方法で構築したクラスタ上で起きた問題は 「当該ソフトウェアの問題ではないか、まずは有名どころのツールを使って構築したクラスタで再現させてほしい」 と言われることがよくあるからです。Kubernetesの公式ツールであればそのような問題は起きにくいでしょう。

ではここからはkindの使い方、および動作のしくみについて説明します。

使い方

必要条件

kindのインストールをするためには次の条件を満たしておく必要があります。

インストール方法

次のコマンドを実行します。

$ GO111MODULE="on" go get sigs.k8s.io/kind@<kindのバージョン番号>

たとえば本書執筆時点における最新版であるv0.4.0を利用するには次のように実行します。

$ GO111MODULE="on" go get sigs.k8s.io/kind@v0.4.0

最も単純なクラスタを作る方法

次のコマンドを実行します。

$ kind create cluster [---name=<クラスタ名。無指定の場合はクラスタ名が"kind"になる>]

作ったクラスタにローカル環境からアクセスできるようにするためには以下のコマンドを実行します。

$ export KUBECONFIG="$(kind get kubeconfig-path [--name=<クラスタ名>])"

クラスタ名のデフォルトはkindです。

下記のように--loglevelオプションを付けると実行時に詳細なログを出力できます。

$ kind create cluster --loglevel debug

--waitオプションを指定すると、ノードがReady状態になるまで待つようになります(デフォルトでは待たない)。

$ kind create cluster --wait 100s

複数クラスタを作る方法

foo, barという二つのクラスタを立ち上げるには次のようにします。

$ kind create cluster --name=foo
Creating cluster "foo" ...
...
$ kind get clusters
foo
$ kind create cluster --name=bar
Creating cluster "bar" ...
...
$ kind get clusters
bar
foo
$ ls -1 .kube/kind-config-*
.kube/kind-config-bar
.kube/kind-config-foo
$ 

マルチノードクラスタを作る方法

kindの設定ファイルを作ればマルチノードクラスタを作れます。次に示すのはコントロールプレーンノード1つとワーカーノード3つのクラスタを作るための設定ファイルです。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
# 1 control plane node and 3 workers
nodes:
# the control plane node config
- role: control-plane
# the three workers
- role: worker
- role: worker
- role: worker

nodes要素以下を変更すればノード数を自由に変えられます。

上記設定ファイルを使ってマルチノードk8sクラスタを作るには次のようにします。

$ kind create cluster --name multi --config multinode.yaml
Creating cluster "multi" ...
...
Cluster creation complete. You can now use the cluster with:

export KUBECONFIG="$(kind get kubeconfig-path --name="multi")"
kubectl cluster-info
$ export KUBECONFIG="$(kind get kubeconfig-path --name="multi")"
$ kubectl get node
NAME                  STATUS   ROLES    AGE     VERSION
multi-control-plane   Ready    master   10m     v1.15.0
multi-worker          Ready    <none>   9m44s   v1.15.0
multi-worker2         Ready    <none>   9m44s   v1.15.0
multi-worker3         Ready    <none>   9m44s   v1.15.0
$ 

Kubernetesのバージョンを指定する方法

kindがインストールするKubernetesのバージョンはkindのバージョンごとにデフォルト値が決まっています(kind v0.4.0ではKubernetes v1.15.0, kind v0.3.0ではkubernetes v1.14.2、など)。 次のようにクラスタ設定ファイルのkubeadmConfigPatchesにバージョンを指定することで、任意のバージョンのKubernetesを使えます。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
kubeadmConfigPatches:
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: ClusterConfiguration
  metadata:
    name: config
  kubernetesVersion: v1.14.3
  networking:
    serviceSubnet: 10.0.0.0/16

kindが作るクラスタを構成する個々のノードはDockerコンテナとして表現されます(詳細は後述)。これらコンテナのイメージを変更するには次のようにクラスタ作成時にイメージを指定します。

$ kind create cluster --image kindest/node:v1.14.3

指定できるイメージの一覧はこちらをごらんください。

ノードコンテナにアクセスする方法

以下のようにdocker execでコントロールプレーンノードに入れます。

$ docker exec -it kind-control-plane bash

コントロールプレーンノード 内では、kubeadm, ctr, crictlなどのコマンドを利用できます。

ログを見る方法

kubectl logsによってkube-apiserverやkube-schedulerのログを確認できます。

kubectl logs -n kube-system kube-scheduler-kind-control-plane

次のコマンドを実行すると各コンポーネントのログを収集してローカルマシンの/tmp以下に書き出してくれます。

$ kind export logs
Exported logs to: /tmp/486658715  

kube-schedulerがpodとして立ち上がっていない場合は、この方法でログを見ることができます。

クラスタの設定を変更する方法

kindは内部でkustomizeを使っており、設定ファイル内にkubeadmConfigPatches、およびkubeadmConfigPatchesJson6902というものを定義できます。 内容はそれぞれkustomizeのpatchesStrategicMergepatchesJson6902に相当します。 これによってkindが内部で利用しているkubeadmの設定ファイルにパッチを当てられます。

例えばkube-schedulerのconfigオプションを指定したい場合は、下記のように記述できます。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
# patch the generated kubeadm config with some extra settingskubeadmConfigPatches:- |
  apiVersion: kubeadm.k8s.io/v1beta1
  kind: ClusterConfiguration
  metadata:name: config
  scheduler:extraVolumes:- name:"config"hostPath: /mnt/host/scheduler
        mountPath: /var/lib/scheduler
        readOnly:trueextraArgs:config: /var/lib/scheduler/scheduler-config.yaml

ローカルマシン上のファイルをコントロールプレーンで利用したい場合は、extraMountsを使えます。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:- role: control-plane
  extraMounts:- containerPath: /mnt/host
      hostPath: /tmp/path/to

kindとkubeadmの設定ファイルについての詳細はそれぞれのgodocを参照してください。

動作のしくみ

実行時にはコントロールプレーンノードとワーカーノードに対応するDockerコンテナがそれぞれ起動されています。それぞれdocker psで確認できます。

$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                  NAMES
df7d631bb6a8        kindest/node:v1.14.1      "/usr/local/bin/entr…"   2 hours ago         Up 2 hours                                                 kind-worker3
4da3bf000b00        kindest/node:v1.14.1      "/usr/local/bin/entr…"   2 hours ago         Up 2 hours                                                 kind-worker2
772938eaed30        kindest/node:v1.14.1      "/usr/local/bin/entr…"   2 hours ago         Up 2 hours          37735/tcp, 127.0.0.1:37735->6443/tcp   kind-control-plane
957d78a62abd        kindest/node:v1.14.1      "/usr/local/bin/entr…"   2 hours ago         Up 2 hours                                                 kind-worker

kindはこれらのコンテナの中でsystemdサービスとしてkubeletを実行しています。

$ docker exec -it kind-control-plane systemctl status kubelet.service
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/kind/systemd/kubelet.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since Fri 2019-06-28 02:38:32 UTC; 1h 52m in ago

etcd, kube-apiserver, kube-scheduler, kube-controller-managerは、 Static PodとしてKubernetes上にセルフホストされています。 kube-proxy, weave, corednsは、DeploymentやDaemonSetとしてKubernetes上にデプロイされています。

$ kubectl get all -A
NAMESPACE     NAME                                             READY   STATUS    RESTARTS   AGE
kube-system   pod/coredns-fb8b8dccf-4gwkb                      1/1     Running   0          113m
kube-system   pod/coredns-fb8b8dccf-xgm69                      1/1     Running   0          113m
kube-system   pod/etcd-kind-control-plane                      1/1     Running   0          112m
kube-system   pod/kube-apiserver-kind-control-plane            1/1     Running   0          112m
kube-system   pod/kube-controller-manager-kind-control-plane   1/1     Running   0          112m
kube-system   pod/kube-proxy-cqh6s                             1/1     Running   0          113m
kube-system   pod/kube-proxy-frxz6                             1/1     Running   0          113m
kube-system   pod/kube-proxy-j9q2r                             1/1     Running   0          113m
kube-system   pod/kube-proxy-kn7xd                             1/1     Running   0          113m
kube-system   pod/kube-scheduler-kind-control-plane            1/1     Running   0          112m
kube-system   pod/weave-net-69crj                              2/2     Running   0          113m
kube-system   pod/weave-net-f8fqv                              2/2     Running   0          113m
kube-system   pod/weave-net-lc7tz                              2/2     Running   0          113m
kube-system   pod/weave-net-q6k67                              2/2     Running   0          113m

NAMESPACE     NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP                  113m
kube-system   service/kube-dns     ClusterIP   10.0.0.10    <none>        53/UDP,53/TCP,9153/TCP   113m

NAMESPACE     NAME                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   daemonset.apps/kube-proxy   4         4         4       4            4           <none>          113m
kube-system   daemonset.apps/weave-net    4         4         4       4            4           <none>          113m

NAMESPACE     NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/coredns   2/2     2            2           113m

NAMESPACE     NAME                                DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/coredns-fb8b8dccf   2         2         2       113m

おわりに

今回はKubernetesの入門、テスト、およびKubernetesなどのOSSへのissue発行において便利なkindというツールを紹介しました。 Necoにおいても自社製ツールに依存しないテストをするためにはこれまでmicrok8sを使っていたのですが、 現在はマルチクラスタ作成機能に注目してkindに移行中です。いまのところ大変便利に使えています。 本記事を読むことによってKubernetesのユーザ、およびkindによってKubernetesを使ったシステムの開発が楽になる人が増えれば幸いです。

*1:これはこれですごく勉強になりますが、それはまた別の話

*2:NecoでもCKEというクラスタ作成/管理ツールを作っています

やっと UIWebView から WKWebView に乗り換えられる話

$
0
0

こんにちは〜! モバイルチームの向井田です。

iOS の UIWebViewが deprecated になって数年、弊社の iOS アプリもようやく WKWebViewに移行できるようになりました。 そこで、今回は私たちが WKWebViewに乗り換えられるようになった理由を語っていきます。

TL;DR

iOS12 から WKWebViewでクライアント証明書を使ったSSL通信が正常に動作するようになりました。

kintone モバイルのリニューアル

kintone モバイルが大幅リニューアルし、UIを一新しました 🎉

https://kintone.cybozu.co.jp/update/main/2019-05.html

リニューアルを期に、コードを Objective-C から Swift ベースに書き直しています。 kintone モバイルは JavaScript カスタマイズをサポートするために、大部分が WebView で構成されています。 iOS8 以上は WKWebViewを使うように Apple からアナウンスされていますが、旧バージョンでは UIWebViewを使い続けていました。 今回のリニューアルで、iOS12 以上であれば WKWebViewを使用するようになりました。

なぜ UIWebView を使い続けていたのか

WKWebViewへの移行には、弊社のクラウドサービスが提供しているセキュアアクセスという機能が関係しています。

セキュアアクセスの説明

弊社のクラウドサービス基盤である cybozu.com が提供するセキュアアクセスは、サーバ側でユーザごとに発行されたクライアント証明書を用いて通信することで、IPアドレス制限がされた環境にも安全にアクセスする仕組みです。

https://www.cybozu.com/jp/service/option/

この機能を kintone モバイルでもサポートするために、WebView のリクエストにクライアント証明書を付与して認証する必要がありました。 旧バージョンでは、NSURLProtocolを継承した CustomHTTPProtocolを使い、UIWebViewの通信をハンドルして実現していました。

https://developer.apple.com/library/archive/samplecode/CustomHTTPProtocol/Introduction/Intro.html

WKWebView ではクライアント証明書の認証がサポートされていたはずだったが…

UIWebViewの後継である WKWebViewでは、公式に Authentication Challenge 時に呼ばれる Delegate が用意されました。

optional funcwebView(_ webView:WKWebView,
           didReceive challenge:URLAuthenticationChallenge,
    completionHandler:@escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->Void)

https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview

WKWebViewが世に出たとき、私たちはもちろんこの Delegate に飛びつきましたが、この Delegate には重大なバグがあります。 iOS11 以下の場合、Delegate の中で PKSCS#12形式の証明書を渡しても、Authentication Challenge に失敗します😱😱😱 つまり、WKWebViewを使ったクライアント証明書認証による通信は実現できませんでした。

上記のバグは Apple のフォーラムでも報告されており、iOS12 でようやく改修されました。 そのため、私達も iOS12 以上の場合に限り、WKWebViewをプロダクトで使用できるようになりました。

WKWebView でクライアント証明書の認証を実装する

めでたく WKWebViewを使ったクライアント証明書による認証が正常動作するようになったので、実装を紹介します。 下のサンプルコードは WKNavigationDelegatewebView(_:didReceive:completionHandler:)を実装した例です。

challenge.previousFailureCountでリクエスト毎の認証チャレンジの失敗回数が取得できます。 失敗の上限を決めておかないと、クライアント証明書が無効のときに無限にリクエストを送り続けてしまうので注意です。

challenge.protectionSpace.authenticationMethodでチャレンジする認証の種類が取得できます。 クライアント証明書での認証時は、NSURLAuthenticationMethodClientCertificateで定義された文字列が challenge.protectionSpace.authenticationMethodに入っています。

クライアント証明書による認証が求められた場合は、クライアント証明書から URLCredentialを生成し、completionHandlerに渡します。

extensionViewController:WKNavigationDelegate {
    funcwebView(_ webView:WKWebView, didReceive challenge:URLAuthenticationChallenge, completionHandler:@escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->Void) {

        // 一度でも認証に失敗したらやり直さないif challenge.previousFailureCount >0 {
            completionHandler(.performDefaultHandling, nil)
        }

        // クライアント証明書での認証が必要if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
            // クライアント証明書の取り出しifletidentity=self.loadCertificate(from:certFilename, password:certPassword) {
                letcredential= URLCredential(identity:identity, certificates:nil, persistence:URLCredential.Persistence.forSession)

                // クライアント証明書による認証にチャレンジする// iOS12 以上でないと正しく動作しない
                completionHandler(.useCredential, credential)
                return
            }
        }
        completionHandler(.performDefaultHandling, nil)
    }

PKSCS#12形式のクライアント証明書ファイル(.pfx ファイル)から URLCredentialを生成するには、ファイルをインポートしてSecIdentityオブジェクトにする必要があります。 SecIdentityPKSCS#12ファイルに含まれるデジタル証明書と秘密鍵がセットになったオブジェクトです。 SecPKCS12Import(_:_:_:)を使って .pfx ファイルを SecIdentityに変換します。

以下のコードは、プロジェクトファイルに予め配置しておいた .pfx ファイルから SecIdentityを生成する例です。

privatefuncloadCertificate(from filename:String, password:String) ->SecIdentity? {
    guardletpath= Bundle.main.path(forResource:filename, ofType:"pfx"),
        letpfxData= try? Data(contentsOf:URL(fileURLWithPath:path))
        else {
            returnnil
    }

    varresult:CFArray?
    letquery= [
        kSecImportExportPassphrase as String:password,
    ]

    // result にインポートした結果が格納されるletstatus= SecPKCS12Import(pfxData as NSData, query as NSDictionary, &result)
    if (status != errSecSuccess) {
        returnnil
    }

    letresultDict= result as? [[String :Any]]
    letidentity= resultDict?.first?[kSecImportItemIdentity as String] as! SecIdentity
    return identity
}

WKWebView のインスタンスに delegate をセットするのを忘れないでください。

classViewController:UIViewController {
    varwebView:WKWebView!overridefuncviewDidLoad() {
        super.viewDidLoad(
        self.webView = WKWebView(frame:self.view.frame)
        self.view.addSubview(self.webView

        // ViewController で Delegate を処理するself.webView.navigationDelegate =self
    }
    ...

ここが便利だぞ WKWebView!

今更ですが、WKWebViewを導入したことで得たメリットを紹介します。

タップ時の遅延が改善される

UIWebViewは、ダブルタップでズームできるようにするために、シングルタップしてから画面が反応するまでに350msほど遅延がありました。 このタップ遅延の問題は多くの開発者を悩ませていたようで、FastClickというサードパーティのライブラリまで登場しました。 UIWebViewで構成された旧バージョンの kintone モバイルでもタップ遅延が発生しており、ユーザの操作感が非常に悪いものになる要因でした。

WKWebViewではこの問題が解消されており、Safariと同様に反応が良いアプリを実現することができます。

https://webkit.org/blog/5610/more-responsive-tapping-on-ios/

HTTPステータスコードがハンドルできる

私たちのようにネイティブアプリ内で WebView を使った UI を提供している場合、WebView が受け取ったレスポンス内に含まれているHTTPステータスコードをハンドルしたいケースがあります。 しかし、UIWebViewは WebView 内のレスポンスが外部から参照できない形になっており、HTTPステータスコードを取得できない仕様でした。

WKWebViewであれば、以下のサンプルのようにして ロードしたいページの HTTP ステータスコードを取得できます。

navigationResponse.responseWKWebViewがロードしようとしているページのレスポンスで、URLResponse型のオブジェクトです。 http通信のレスポンスであれば、navigationResponse.responseURLResponseクラスのサブクラスであるHTTPURLResponseに置換できます。 この HTTPURLResponseが持っている statusCodeプロパティが HTTP ステータスコードです。

funcwebView(_ webView:WKWebView, decidePolicyFor navigationResponse:WKNavigationResponse,
             decisionHandler:@escaping (WKNavigationResponsePolicy) ->Void) {
    ifletresponse= navigationResponse.response as? HTTPURLResponse {
        if response.statusCode ==200 {
            print("success💯")
        }
        if response.statusCode >300 {
            print("failure...🤮")
        }
    }
    decisionHandler(.allow)
}

まとめ

UIWebViewはパフォーマンスやセキュリティの観点で様々な問題があるので、より安全で高速な WKWebViewに乗り換えていきましょう!

kintoneのポータルデザイン開発ストーリー

$
0
0

デザイン&リサーチグループの篠原です。昨年から携わってきたプロジェクトが来週正式リリースということで、メイキング的な話をしたいと思います!

このプロジェクトとは?

2019年7月版搭載予定のkintoneポータルカスタマイズ機能を活用して、ポータルを手軽にカスタマイズできるようにするプロジェクトです。 カスタマイズされたポータルのサンプル画像

米国でリサーチを実施

2018年9月。kintonePMの柴田が米国でサイトビジットを実施したところからプロジェクトは始まります。現地企業9社を訪問してkintoneの使い方をリサーチしました。 なぜ米国でリサーチをするのか?というと、サイボウズは「チームワークあふれる社会を創る」を理念に掲げていて、kintoneを世界中に広めたいからです。 現地企業の外観

ポータルへの不満が高いことが明らかに

リサーチの結果、各社とも同じような悩みや不満を抱えていました。

  • 目的のアプリを見つけにくい(並べ替えしたい)
  • ファーストビューにもっと多くの情報を表示したい
  • 色合いやアイコンをもっとデザインしたい
  • スペースは使っていないから欄を消したい
  • 必要ないアプリは表示させたくない
  • ポータル画面をカスタマイズしたい
  • お知らせ欄の自由度が低い
  • 社員が使ってくれるようにポータルをわかりやすくしたい
  • デザインが古い

コンセプトは「キャンバス」

細かな個々の要望に対応すると時間と工数がかかりすぎて現実的ではない…。開発コストを抑えつつユーザーの悩みを解決できる現実的なアイデアが必要でした。 開発メンバーとディスカッションを繰り返す中で、HTML / CSS / JSで自由にキャンバスをカスタマイズできるようにするというアイデアが生まれました。

「誰に、何と言ってほしいか」という軸で考える

コンセプトが決まった段階でターゲットに何と言ってほしいかを明確にしました。そうすることで社内外のメンバーに説明しやすくなるだけでなく、開発を進める中で方向性があっているか確認するための道しるべにもなります。 メインターゲットはポータルをカスタマイズしている既存パートナーで、公式なカスタマイズになって安心。サブターゲットはポータルに不満を持っているkintone管理者で必要な情報だけ表示できてうれしい。

多くの人に使ってもらうために

キャンバスに描くための「ツール」を用意

ポータルを手軽にカスタマイズできるツールとしてKintone Portal Designer(Chrome拡張)を製作しました。機能を一言で説明するとHTML / CSS / JSが書けるシンプルなエディタです。コードを書いて保存するとその内容をポータルに反映することができます。 キントーンポータルデザイナーの画面

chrome.google.com

社内勉強会を繰り返しUIを改善

一通り機能ができた段階で社内勉強会を開催して、使いやすさの検証を行いました。サイボウズはフィードバックを元に改善をおこなうことを大切にしています。 検証の結果を受けて、快適にコードが書けるようにMonaco Editor*1を導入したり、デザインテンプレートのインポートを簡単にする改善をしました。 改善前と改善後のキントーンポータルデザイナーの画面

多様な使われ方を考慮

デベロッパー、デザイナー、視覚に障害を持った方も使うかもしれない。デスクに座ってコードを書く場合もあれば、営業先でデモをする可能性もある。多様な使われ方に配慮することが、最終的にアクセシビリティに繋がるという思いを込めて、キーボードのみで操作が完結できるようにしたり、画面の拡大縮小をしても画面が崩れないようにUIを設計しました。

デザインテンプレートを製作

ポータルをカスタマイズする際、一からHTML / CSS / JSを書くというのは大変です。そこで、コードが書けない人でも簡単に使えるようにデザインテンプレートを配布して敷居を下げることにしました。まずは、どんなテンプレートが良さそうか様々なデザインのポータルを作り社内のメンバーに見せてフィードバックを募りました。 様々なテイストのプロトタイプ

そして改善を繰り返し最終的に5つのデザインテンプレートが完成しました!Githubで公開しているのでkintoneをお使いの方はぜひ試してみてください。 github.com

プロトタイプのフィードバックをもらう

日本、中国、米国でヒアリング

デザインテンプレートを見せて、第一印象についてヒアリングしました。その結果以下のようなヒントが得られました。

  • 業務をシンプルに整理
  • UIも整理した業務に合わせて、必要な要素だけを表示
  • 一目で何ができるかわかるようなデザイン
  • 愛着のわくデザイン、毎日見ても飽きないデザイン 中国でのヒアリングの様子

「これいいね!」という声が上がる

ヒントを元に、米国のセールスメンバーが営業で使っているkintoneのポータルをカスタマイズしたところ「これいいね!」というフィードバックを沢山いただきました。 米国では見た目が古く、不要な情報が多いという不満があり、そこを改善できたことがポジティブなフィードバックにつながったようです。 改善前と改善後のポータル画面

目標は世界中に広めること

デザイン&リサーチグループは世界中のチームに使ってもらえるようなkintoneポータルを提供し、世界中のチームワークの向上に貢献したいと考えています。 毎日ワクワクして使ってもらえるポータル、毎日使っても飽きないデザインを探求するために来週サンフランシスコに行ってリサーチしてきます。 このリサーチの様子は後日こちらのブログで報告する予定です。お楽しみに!

海外で仕事がしたいリサーチャー募集中!

現在デザイン&リサーチグループは「海外で仕事がしたい!」というリサーチャーを大募集中です。興味のある方のご応募をお待ちしております! cybozu.co.jp

*1:Monaco Editorとはマイクロソフトが開発したオープンソースのエディターです。


Test Everything: データセンター仮想化と自動テストの取り組み

$
0
0

こんにちは。Necoプロジェクトの池添(@zoetro)です。

Necoプロジェクトでは、自社データセンター上のインフラ構築の仕組みを開発しており、サーバーのプロビジョニング、Kubernetesクラスタの構築、Kubernetes上で動くアプリケーションのデプロイ、各種ソフトウェアやOSのアップグレードなどをすべて自動化しています。

blog.cybozu.io

このプロジェクトでは「データセンターの機能をすべてテストすること」を設計方針のひとつとしており、データセンターをまるごと仮想化してテストし、人の手を介することなく成果物の継続的インテグレーションとデリバリーを実現しています。 最初にこの方針を決めたとき、チーム内でも議論がありました。本当にデータセンターをまるごと自動テストできるのか?QAチームの手動テストをおこなわずに品質を保証することができるのか?など多くの疑問が湧きました。

私自身もプロジェクト開始時には半信半疑だったのですが、現在ではテストを自動化することが当たり前になっており、日々たくさんのテストを書いて不具合を検出しています。 本記事では、我々がどのようにデータセンターをまるごと仮想化し、自動テストの仕組みを構築していったのかを紹介します。

手動テスト vs. 自動テスト

Necoプロジェクトでは非常にたくさんのOSSを利用しています。 これらのOSSは進化が速く、例えばKubernetesは3ヶ月に1度リリースされて数多くの変更がおこなわれます。 各種ソフトウェアのアップグレードをおこなう前にはリリースノートのチェックもおこないますが、実際にはリリースノートに記述されていない破壊的変更も存在するためテストは必須です。 このような状況でバージョンアップのたびに手動でテストをしていたのではとても間に合わないでしょう。 また、重大な脆弱性が見つかった場合にはゆっくりとテストしている時間もありません。

さらに、サーバーのプロビジョニングやKubernetesクラスタ構築の仕組みをテストするためには、ネットワークやストレージ、LinuxカーネルからKubernetesが提供する機能まで幅広い知識が必要となります。 ここで開発とテストを分離してしまうと、開発者はテストを考慮した設計や実装をしにくくなり、テスト担当者はキャッチアップするのに多くの時間を要するため非効率となってしまうでしょう。 そこで、開発者が自分たちでテストを書いて品質に対して責任をもつことがもっとも効率的であると言えます。

以上のことから、開発者がテストを自動化し継続的デリバリーの仕組みを実現することは、我々のプロジェクトでは必須条件であると考えました。

データセンターをまるごとテストする難しさ

インフラのテストの難しさの1つがテスト環境を用意することです。

Necoプロジェクトでは、大規模なKubernetesクラスタに適したネットワークを実現するために、BGPをベースとしたネットワークを構築し、CNIプラグインも自作しています。 そのため、データセンターと同じネットワーク構成の環境を用意しないとテストすることができません。 また、分散システムの耐障害性をテストするためには最低3ラックの構成が必要となり、ネットワーク機器やサーバーのコストを考えると複数のテスト環境を用意することは難しいでしょう。

そこで、データセンターをソフトウェアで仮想化して1台のサーバー上でインフラ構築のテストを実施することにしました。 まず仮想データセンターを簡単に構築できるようにするために、placematというツールを開発しました。 placematを利用するとVMやコンテナを利用して任意の構成の仮想データセンターを立ち上げることができます。Necoプロジェクトでは下図のような仮想データセンターをテストに利用しています。

仮想データセンターをテストに利用している図

  1. Boot Server(管理用サーバー)およびCompute Server(計算用サーバー)を仮想マシンとして起動します。この時Boot ServerのOSにはUbuntuを利用し、Compute ServerはOSのない状態で起動します。
  2. 次にネットワークスイッチを模擬するためのコンテナを立ち上げます。スイッチはBGPリフレクタ、DHCP Relay、NTPサーバー、Webプロキシサーバーなどの多くの機能を持っていますが、複数のソフトウェアを組み合わせてそれらの機能を実現しています。
  3. ip linkコマンドでveth(Virtual Ethernet)を作成したり、iptablesの設定をおこなってネットワークの構成を模擬します。ネットワークの冗長化も実現しています。

この仮想データセンターに対して、自動テストを実行していくことになります。

テストシナリオ

仮想データセンターに対して、具体的にどのようなテストシナリオを流しているのか解説していきます。

まず、Boot Server上にnecoというツールをセットアップします。 このツールはBIOSやネットワークの設定、bird, chrony, serfなどのシステムソフトウェアのセットアップ、etcd, Vault, sabakan, CKEなどインフラ構築自動化ツール群のセットアップをおこないます。

sabakanは、我々が開発しているサーバーのプロビジョニングツールです。 各Compute Serverがsabakanに問い合わせてCoreOSでネットブートし、必要なシステムソフトウェアのセットアップをおこないます。

CKEも我々が開発しているツールで、Kubernetesクラスタの構築および運用を自動化しています。 CKEはsabakanがプロビジョニングしたCompute Server上にKubernetesクラスタを構築します。このときCNIプラグインやCoreDNSや各種ポリシーなど、Kubernetsクラスタの立ち上げに必要なアプリケーションのデプロイや設定をおこないます。 ここでKubernetesクラスタが正しく構築できていることをチェックします。

ここからサーバー故障、サーバー追加、OSや各種ソフトウェアのアップグレード、全台電源停止状態からの復旧などのテストをおこないます。 さらに、構築したKubernetesクラスタ上に継続的デリバリの仕組みを構築し、ロードバランサーや証明書自動発行、モニタリングなどのアプリケーションのデプロイや、アップグレードの動作確認を実施します。

このような一連のテストを1日に何回も実施しています。

テストサイズの定義

仮想データセンターを利用したテストの実施には現在のところ約1時間程度かかっています。 このテストにむやみにテストケースを増やしてしまうと、テスト時間はすぐに増大してしまいます。

そこで我々はテストピラミッドの考え方を取り入れてテストサイズを定義し、仮想データセンターを利用したテストが増大しないように注意しています。

Googleの事例では、Large Test, Middle test, Small Testのような分類がおこなわれています。 これに倣って我々は以下のようなテストの分類をおこないました。

Single-Host Test、Multi-Host Test、Data Center Testの図解

  • Data Center Test: データセンターを丸ごと仮想化し、本番と同じ構成で試験する
  • Multi-Host Test: 複数台のホスト上で複数のコンポーネントを組み合わせて試験する
  • Single-Host Test: 1台のホスト上で1つのコンポーネントの機能を試験する

上位のテストはシステム全体が正しく動くことを確認できるメリットがありますが、実行時間が長くメンテナンスコストも大きいため、できる限り小さく保つ必要があります。 下位の層のテストで網羅する範囲を増やし、どうしてもテストできない項目だけ上位の層でテストすることが望ましいとされています。 我々も、各ソフトウェアの機能は出来るだけ下位の層でテストをおこなうようにしています。

これまでに検出した不具合

テストの自動化により、これまでに数多くの不具合を検出することができています。 これまでに検出できた不具合の一例を以下に示します。 いずれももし本番発生していたらと思うとゾッとするような不具合ばかりです。

  • BGPで複数経路の設定をおこなった場合に、送信パケットの送り元IPアドレスがNICのIPアドレスではなく別のものになる問題
  • etcdの先頭のメンバーがクラッシュしたときに、etcdに接続できなくなる問題
  • サーバー再起動後にCNIプラグインのアップグレードをすると、アドレスプールが解放されてしまう問題
  • argo-cdのアップグレードによりアプリケーションの適用順序が変わり、デプロイに失敗する問題
  • CoreOSのアップデートによりrkt fetchが動かなくなる問題

テストしていないものは動かない

「データセンターすべての機能をテストすること」を方針として掲げていますが、現実には仮想化されたデータセンターではテストできない(しにくい)部分もあります。 しかしこれまでの経験上、テストできない(しにくい)からと言ってテストしていない機能は、本物のデータセンターで動かしたときにほぼ確実に問題が発生しました。

例えばBMCによる電源管理、Redfishによるハードウェアモニタリング、OMSAによるBIOSセットアップなどは仮想データセンターに含めていなかったため、それらを利用した機能を本物のデータセンターで構築したときに正しく動きませんでした。

これらの問題が発生した後は、BMCを模擬してサーバーの電源管理がおこなえるような仕組みを作ったり、モックサーバーを作って本物と同じようなレスポンスを返すようにしたり、出来るだけ本番環境と近くなるような仕組みをテストに組み入れています。 またCloud DNSやLet’s Encryptなどの外部サービスを利用している部分は、本物を使ってテストするようにしています。 このように、仮想データセンターと実際のデータセンターの差異を出来るだけ小さくしていくことが重要だと感じています。

まとめ

Kubernetesとそれに関連するOSSの進化はとても速いため、手動で試験をしていては更新に追いつけずあっという間にメンテナンスできない状態になりかねません。 すべての機能試験を自動化するのは大変で、我々の開発工数の半分近くはテストに費やしています。 しかし現実にKubernetes中心のシステムを運用し、維持発展させていくためには自動テストは不可欠です。 我々の取り組みがひとつの参考となれば幸いです。

Kubernetesアプリケーションの開発、デバッグを高速化するツール、Telepresenceの紹介

$
0
0

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

本記事ではKubernetes(以下K8sと記載)アプリケーション(以降アプリと記載)の開発を高速化するツール、Telepresenceを紹介します。

最初に結論を書いておくと、Telepresenceは次のようなツールです。

  • ローカルで動くプロセスやコンテナをk8sクラスタの中で動かせる
  • 既存のDeployment内のコンテナを上記ローカルコンテナで置き換えられる
  • テストやデバッグのためにいちいちコンテナイメージをレジストリにpush,そこからpull…とする必要がないので開発速度が上げられる

Telepresenceは現在Cloud Native Computing FoundationのSandBoxプロジェクトです。

Telepresence登場の背景

前節において"開発を高速化する"と書きましたが、まずはTelepresenceを使わないときの開発、デバッグはどのようになっているのかについて述べます。あるアプリがKubernetesクラスタにdeployされている状態を初期状態とすると、次のようになります。

  1. アプリのソースに変更を加える
  2. アプリのコンテナイメージを作る
  3. Dockerhubなどのレジストリにコンテナイメージをpushする
  4. deploymentの変更やkubectl set imageなどによってアプリが変更後のコンテナイメージを使うようにする
  5. アプリのPodを変更後のコンテナイメージを使って再起動する

このときコンテナイメージのpushからPodの再起動までにはそれなりの時間待たされるため、一回ソースを変更してからそれを動かすまでのサイクルが長くなりがちというのがK8sアプリ開発の大きな問題です。コンテナイメージのサイズが大きいほどこのサイクルは長くなります。この部分を高速化してくれるのがTelepresenceです。

次節以降はTelepresenceがどういうものなのかを実際に使いながら紹介します。

インストール方法

公式サイトの手順に従ってインストールします。Ubuntu 18.04の場合は次のコマンドを使います。

$ curl -s https://packagecloud.io/install/repositories/datawireio/telepresence/script.deb.sh | sudo bash
$ sudo apt install --no-install-recommends telepresence

サンプル1: ローカルコンテナをK8sクラスタ内で動かす

まずはTelepresenceの基本機能である、ローカルコンテナをK8sクラスタ内で動かす方法について書きます。

step 1: K8sクラスタの作成

kindを使ってK8sクラスタを作ります。ここではクラスタの作成にkindを使います。kindのインストール方法については以前紹介したkindについての記事を参照ください。

$ kind create cluster
$ export KUBECONFIG="$(kind get kubeconfig-path)"

step 2: サービスをdeploy

以下のhello.yamlというマニフェストを使ってhellow-world-web-serverというアプリをクラスタ上で動かします。

apiVersion: apps/v1
kind: Deployment
metadata:name: hello-world-web-server
spec:replicas:1selector:matchLabels:app: hello-world-web-server
  template:metadata:labels:app: hello-world-web-server
    spec:containers:- name: hello-world-web-server
        image: quay.io/satoru_takeuchi/hello-world-web-server:0.1
        ports:- containerPort:8000---apiVersion: v1
kind: Service
metadata:name: hello-world-web-server
spec:selector:app: hello-world-web-server
  ports:- protocol: TCP
    port:8000targetPort:8000

これはport 8000にアクセスすると"hello, world!"という文字列を返すというアプリです。

次のようにdeployした上でservice化します。

$ kubectl create -f hello.yaml
...
$ kubectl get service
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
hello-world-web-server   ClusterIP   10.109.22.168   <none>        8000/TCP   37s
kubernetes               ClusterIP   10.96.0.1       <none>        443/TCP    104m
$ 

ここまでで、クラスタ内部からはこのアプリにhello-world-web-serverという名前、ないし10.109.22.168によってアクセスできます。その一方でこの設定ではクラスタの外からはアクセスできません。

step 3: telepresenceによってクラスタ上でローカルプロセス/コンテナを実行

ローカルプロセスcurlからhello-world-web-serverのport 8000にアクセスしてみます。

$ curl http://hello-world-web-server:8000
curl: (6) Could not resolve host: hello-world-web-server
$ 

クラスタの外からのアクセスなので失敗しました。これは想定通りの動きです。

ローカルプロセスからクラスタにアクセスできない
ローカルプロセスからクラスタにアクセスできない

続いてTelepresenceを介して同じプロセスを動作させるとどうなるでしょうか。

$ telepresence --run curl http://hello-world-web-server:8000
T: Starting proxy with method 'vpn-tcp', which has the following limitations: All processes are affected, only one telepresence can run per machine, and you can't use other VPNs. You may need to add cloud hosts and headless services with --also-proxy. For a full list of method limitations see
T: https://telepresence.io/reference/methods.html
T: Volumes are rooted at $TELEPRESENCE_ROOT. See https://telepresence.io/howto/volumes.html for details.
T: Starting network proxy to cluster using new Deployment telepresence-1562306285-3227046-17962

T: No traffic is being forwarded from the remote Deployment to your local machine. You can use the --expose option to specify which ports you want to forward.

T: Setup complete. Launching your command.
Hello world!                         ☆ サービスにアクセスできている
T: Your process has exited.
T: Exit cleanup in progress
T: Cleaning up Deployment telepresence-1562306285-3227046-17962

アクセスが成功しました。本節冒頭で述べたようにTelepresenceによってローカル環境にあるプロセス(および後述するコンテナ)がクラスタ内にいるかのように扱えることがわかりました。

ローカルプロセスからクラスタにアクセスできる
ローカルプロセスからクラスタにアクセスできる

このときTelepresenceは実行ログをtelepresence.logに出力します。

同様にクラスタ内でshellを動かすこともできます。

$ telepresence --run-shell
T: Starting proxy with method 'vpn-tcp', which has the following limitations: All processes are affected, only one telepresence can run per machine, and you can't use other VPNs. You may need to add cloud hosts and headless services with --also-proxy. For a full list of method limitations see
T: https://telepresence.io/reference/methods.html
T: Volumes are rooted at $TELEPRESENCE_ROOT. See https://telepresence.io/howto/volumes.html for details.
T: Starting network proxy to cluster using new Deployment telepresence-1562306514-402358-21367

T: No traffic is being forwarded from the remote Deployment to your local machine. You can use the --expose option to specify which ports you want to forward.

T: Setup complete. Launching your command.
$ curl http://hello-world-web-server:8000
Hello world!

shellを含むプロセスだけではなく、クラスタ内でコンテナを動かすこともできます。

$ telepresence --docker-run -it --rm pstauffer/curl curl http://hello-world-web-server:8000
T: Volumes are rooted at $TELEPRESENCE_ROOT. See https://telepresence.io/howto/volumes.html for details.
T: Starting network proxy to cluster using new Deployment telepresence-1562306747-6756666-25541

T: No traffic is being forwarded from the remote Deployment to your local machine. You can use the --expose option to specify which ports you want to forward.

T: Setup complete. Launching your container.
Hello world!
T: Your process has exited.
T: Exit cleanup in progress
T: Cleaning up Deployment telepresence-1562306747-6756666-25541
$ 

ローカルコンテナからクラスタにアクセスできる
ローカルコンテナからクラスタにアクセスできる

サンプル2: deployment内のコンテナをローカル環境で動作するコンテナで差し替える

初期状態はサンプル1の終了時点であるとします。

通常は実行中のアプリのコンテナを差し替えるには前述のようにコンテナイメージをビルドした後に次のような手順が必要です。

  1. コンテナイメージをレジストリにpush
  2. deploymentを書き換え or kubectl set imageによってアプリが使うコンテナを変更する
  3. アプリのpodを再起動してコンテナをリロード

これに対してTelepresenceを使う場合はアプリが使うコンテナをローカルで動くものに差し替えられます。別のいいかたをすると上記の手順をすべて省略できます。

step 1: 変更版のコンテナをビルドする

step 1.1: ソースの準備

hello-world-web-serverコンテナのソースをダウンロードしてhello-world-web-server:0.1に相当するバージョンをチェックアウトします。

$ git clone https://github.com/satoru-takeuchi/hello-world-web-server.git
...
$ cd hello-world-web-server
$ git checkout v0.1
...
$ 

step 1.2: メッセージを差し替え

アプリのソースコード変更によって"Hello world!"というメッセージを"Hello telepresence!"で差し替えます。

$ cat index.html
Hello world!
$ sed -i -e s/world/Telepresence/ index.html
$ cat index.html
Hello Telepresence!
$ 

step 1.3: コンテナイメージをビルド

次のようにコンテナイメージをビルドします。

$ docker build -t hello-world-web-server:dev .
...
Successfully tagged hello-world-web-server:dev
...
$ 

step 2: アプリ内のコンテナをローカルのものと差し替える

次のようなコマンドを発行します。

$ telepresence --swap-deployment hello-world-web-server --docker-run --rm -it hello-world-web-server:dev
T: Volumes are rooted at $TELEPRESENCE_ROOT. See https://telepresence.io/howto/volumes.html for details.
T: Starting network proxy to cluster by swapping out Deployment hello-world-web-server with a proxy
T: Forwarding remote port 8000 to local port 8000.

T: Setup complete. Launching your container.
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
...

この後、別端末でk8sクラスタ内からhello worldサービスにアクセスすると差し替え後のコンテナを使っているのがわかります。

$ export KUBECONFIG="$(kind get kubeconfig-path)"
$ kubectl run access-hello-world-web-server -it --rm --image=pstauffer/curl --restart=Never -- curl http://hello-world-web-server:8000
Hello Telepresence!                           ☆ メッセージが変更されている
pod "access-hello-world-web-server" deleted
$ 

下図において、通常は点線の矢印のようにアクセスするはずですが、Telepresenceによって実線の矢印のようにアクセスするようになります。

変更したコンテナとDeployment内のコンテナを差し替えられる
変更したコンテナとDeployment内のコンテナを差し替えられる

最後に環境を綺麗にしておきましょう。

$ kubectl delete -f hello.yaml

サンプル3: 複数コンテナから成るdeployment内のコンテナの差し替え

サンプル2の最後の状態で次に示すmulti-container.yamlというマニフェストを読み込みます。このDeploymentの中にはhello.yamlの場合に加えてsleepし続けるdummyというコンテナが存在します。

apiVersion: apps/v1
kind: Deployment
metadata:name: hello-world-web-server
spec:replicas:1selector:matchLabels:app: hello-world-web-server
  template:metadata:labels:app: hello-world-web-server
    spec:containers:- name: hello-world-web-server
        image: quay.io/satoru_takeuchi/hello-world-web-server:0.1
        ports:- containerPort:8000containers:- name: dummy
         image: quay.io/satoru_takeuchi/hello-world-web-server:0.1
         command:["/bin/sh"]args:["-c", "while sleep 1000 ; do : ; done"]---apiVersion: v1
kind: Service
metadata:name: hello-world-web-server
spec:selector:app: hello-world-web-server
  ports:- protocol: TCP
    port:8000targetPort:8000

Telepresenceはコンテナを特定するためにdeployemt名を指定する際に<deployment名>:<コンテナ名>という書き方ができます。

$ kubectl create -f multi-container.yaml
...
$ telepresence --swap-deployment hello-world-web-server:hello-world-web-server --docker-run --rm -it hello-world-web-server:dev
...
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

ここで別端末からhello-world-web-serverにアクセスすると変更後のコンテナが見えます。

$ kubectl run access-hello-world-web-server -it --rm --image=pstauffer/curl --restart=Never -- curl http://hello-world-web-server:8000
Hello Telepresence!                    ☆ メッセージが変わっている
pod "access-hello-world-web-server" deleted

最後に環境を綺麗にしておきます。

$ kubectl delete -f multi-container.yaml

おわりに

弊社におけるTelepresenceを用例を一つ紹介しておきます。弊社には日本国内のインフラ基盤を刷新するNecoプロジェクトとは別に、US向けのインフラ基盤を刷新するYakumoというプロジェクトもあります。こちらでもTelepresenceがすでに活発に使われています。

Yakumoで提供されるkintoneは国内のデータセンターで提供されていたkintoneと構成が異なるため、AWS移行と同時にkintoneのテストケースの修正も発生します。Yakumo上でのkintoneのテストはKubernetes Jobとして実行しています。このテストのテストケース数は数千にも及ぶので、テストを修正する度にJobをデプロイしてると確認に時間がかかります。そこでYakumoチームではTelepresenceを使って特定のテストケースだけをローカル環境で実行することによって、待ち時間を大幅に削減できています。

Necoプロジェクトにおいても開発が進むにしたがってk8sアプリのサイズが多く、かつ、それぞれのサイズ大きくなってきたことにより、今後ますますTelepresenceを使う場面が増えていく見込みです。

最後になりますが、K8sアプリの開発において本記事で述べたような問題をかかえているみなさまは、ぜひTelepresenceを試していただきたいとおもいます。

TeleportでKubernetesクラスタへのユーザーアクセスを管理する

$
0
0

こんにちは、Necoプロジェクトの池添(@zoetro)です。

今回はTeleportというツールを利用して、Kubernetesクラスタへのユーザーアクセスを管理する方法を紹介します。

TL;DR

TeleportとKubernetesを連携させることで、以下のような仕組みを実現することができます。

  • ユーザーが踏み台サーバーを経由してKubernetesクラスタにアクセスできる
  • Kubernetesリソースへのアクセス権を統合的に管理することができる
  • kubectl execの内容はセッションレコードとして保存されリプレイ再生することも可能
  • kubectlの証明書の有効期限を短くすることでリスクを低減

Teleportとは

Teleportは、簡単に言ってしまうと従来のSSHの踏み台サーバー(Bastion) をクラウドネィティブ 時代に合わせて進化させたものです。TeleportはGravitational社が開発しています。 Teleportによって次のようなことができます。なお、無償版では一部の機能が利用できません。

  • 踏み台サーバー経由でのSSHやSSH-over-HTTPSによるLinuxサーバ群へのアクセス
  • 踏み台サーバー経由でのKubernetes APIによるKubernetesクラスタへのアクセス
  • 様々なSSOとの連携(無償版はGitHub OAuthのみ)
  • SSHのAudit Logの保存
  • SSHやkubectlのセッションレコードの保存
  • RBACによるSSHユーザーの統合的なアクセス権限管理(有償版のみ)
  • RBACによるKubernetesユーザーの統合的なアクセス権限管理

なお、SSHの踏み台サーバーとしての解説は、以下の記事がわかりやすいのでおすすめです。

blog.animereview.jp

Kubernetesクラスタのアクセス権管理

Kubernetesクラスタを利用しているみなさんは、どのようにユーザーのアクセス権限を管理しているでしょうか?

小さなクラスタであれば管理者と開発者の権限を分ける必要はないかもしれません。

しかしクラスタの規模が大きくなり、複数のチームが1つのKubernetesクラスタを利用するような場合、 アクセス権限管理は重要になります。

例えば、開発者がクラスタレベルのリソースや他のチームのリソースにアクセスできないようにする必要があります。 また、NetworkPolicyやPodSecurityPolicyなどのポリシーに関しては、誤って書き換えられないように しておかないとセキュリティインシデントにも繋がってしまいます。

そこで、Kubernetesの認証認可の仕組みを利用することになります。

Kubernetesは様々な認証方式に対応しています(Authenticating - Kubernetes)。 そして認証されたユーザーやサービスアカウントに、必要なアクセス権限を割り当てることができます。(Using RBAC Authorization - Kubernetes)。

しかし、ユーザーのアクセス権に応じて適切な証明書やトークンを発行する仕組みを構築するのは面倒です。 また、証明書やトークンの有効期限を長くしておくとそれらが漏洩した時のリスクも大きくなるため、 有効期限を短くして定期的に再発行する仕組みについても考えなくてはなりません。

Teleportを利用すると、SSO そのような仕組みを簡単に構築することができます。

試してみよう

Teleportを利用したKubernetesのユーザー管理の仕組みを試してみましょう。

ここでは、下記のバージョンのソフトウェアを利用します。

  • kind: 0.4.0
  • Kubernetes: 1.15.0
  • Helm: 2.14.2
  • Teleport: 4.0.2 (無償版を利用)
  • cert-manager: 0.6.2

利用しているファイルは下記のリポジトリで公開しています。

github.com

Kubernetesクラスタの作成

kindを利用してKubernetesクラスタを作成します。 kindの使い方に関してはこちらの記事をごらんください。

まず下記のようなファイルを作成しcluster.yamlという名前で保存します。 今回はTeleportのサービスにホストマシンからアクセスできるようにNodePortサービスを利用するので、 extraPortMappingsの設定をしておきます。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:- role: control-plane
- role: worker
  extraPortMappings:- containerPort:30023hostPort:3023- containerPort:30026hostPort:3026- containerPort:30080hostPort:3080

下記のコマンドを実行してクラスタを作成します。

$ kind create cluster --name teleport-demo --config cluster.yaml 

また、kindで作成したクラスタにアクセスできるようにKUBECONFIG環境変数を設定しておきます。

$ export KUBECONFIG="$(kind get kubeconfig-path --name="teleport-demo")"

Helmのセットアップ

cert-managerとTeleportをセットアップするためにHelmを利用します。 Helmの公式ドキュメントに従って、HelmのCLIをインストールしてください。

次に以下のようなファイルを作ってhelm-account.yamlという名前で保存します。

apiVersion: v1
kind: ServiceAccount
metadata:name: helm
  namespace: kube-system
---apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: helm
roleRef:apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:- kind: ServiceAccount
  name: helm
  namespace: kube-system

下記のコマンドを実行して、HelmをKubernetesクラスタにデプロイします。

$ kubectl apply -f helm-account.yaml
$ helm init --service-account helm
$ helm repo update

cert-manager

次にcert-managerをKubernetesクラスタにデプロイします。

cert-manager chartのドキュメントに従って、下記のようにデプロイします。

$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml
$ kubectl create namespace cert-manager
$ kubectl label namespace cert-manager certmanager.k8s.io/disable-validation="true"
$ helm install --name cert-manager --namespace cert-manager stable/cert-manager

次にTeleport用の証明書を発行します。

まず下記のyamlをcertificate.yamlという名前で保存します。

apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:name: selfsigning-issuer
spec:selfSigned:{}---apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:name: teleport-tls
  namespace: teleport
spec:secretName: tls-web
  commonName: teleport.example.com
  isCA:trueissuerRef:name: selfsigning-issuer
    kind: ClusterIssuer

下記のコマンドを実行して証明書を発行します。

$ kubectl apply -f certificate.yaml

なお、ここではself signed証明書を発行していますが、実環境で利用する場合はLet's Encryptなどを利用して証明書を発行するのがよいでしょう。

Teleport

さて、いよいよTeleportをデプロイします。 サンプルとして用意されているHelm Chartを利用することにします。

$ git clone https://github.com/gravitational/teleport.git $GOPATH/src/github.com/gravitational/teleport

ただしこのサンプルは有償版のTeleportを利用するようになっているので、無償版を利用するように以下のようにvalues.yamlを書き換えます。 また、サービスをClusterIPからNodePortに変更します。

image:repository: quay.io/gravitational/teleport
  tag: 4.0.2
  pullPolicy: IfNotPresent
strategy: RollingUpdate
service:type: NodePort
  ports:proxyweb:port:3080targetPort:3080nodePort:30080protocol: TCP
    authssh:port:3025targetPort:3025nodePort:30025protocol: TCP
    proxykube:port:3026targetPort:3026nodePort:30026protocol: TCP
    proxyssh:port:3023targetPort:3023nodePort:30023protocol: TCP
ingress:enabled:falseports:proxyweb:containerPort:3080authssh:containerPort:3025proxykube:containerPort:3026proxyssh:containerPort:3023nodessh:containerPort:3022proxytunnel:containerPort:3024proxy:tls:enabled:truesecretName: tls-web
license:enabled:falseconfig:teleport:log:output: stderr
      severity: DEBUG
    data_dir: /var/lib/teleport
    storage:type: dir
  auth_service:enabled: yes
    authentication:type: github
    public_addr: teleport.example.com:3025
    cluster_name: teleport.example.com
  ssh_service:enabled: yes
    public_addr: teleport.example.com:3022
  proxy_service:enabled: yes
    public_addr: teleport.example.com
    web_listen_addr: 0.0.0.0:3080
    listen_addr: 0.0.0.0:3023
    https_key_file: /var/lib/certs/tls.key
    https_cert_file: /var/lib/certs/tls.crt
    kubernetes:enabled: yes
      listen_addr: 0.0.0.0:3026
rbac:create:trueserviceAccount:create:truepersistence:enabled:falseautomountServiceAccountToken:true

下記のコマンドでTeleportをデプロイします。

$ kubectl create namespace teleport
$ helm install --name teleport --namespace teleport -f values.yaml $GOPATH/src/github.com/gravitational/teleport/examples/chart/teleport/

GitHub連携

Teleportは有償版は様々な認証方式が利用できますが、無償版ではパスワード認証とGitHub OAuthしか利用できません。 今回はGitHub OAuth認証でKubernetesと連携する方法を紹介します。

TeleportのKubernetes連携機能では、GitHubのTeamとKubernetesのGroupをマッピングすることができます。

まずGitHubにOrganizationとTeamを用意してください。OrganizationのOwner権限が必要となります。

次にCreating an OAuth Appの手順に従って、GitHubにOAuth Appの登録をおこないます。

入力項目は下記のようにしてください。callback URLにはブラウザからアクセス可能なTeleportのアドレスを指定する必要があります。

  • Application name: Teleport
  • Homepage URL: https://teleport.example.com
  • Authorization callback URL: https://teleport.example.com:3080/v1/webapi/github/callback

登録後に表示されるClient IDClient Secretを利用して、下記のようなgithub.yamlファイルを作成します。 organizationteamには、GitHubのOrganizationとTeamを指定します。 kubernetes_groupsにはKubernetesクラスタのグループ名を指定します。 system:mastersを指定するとKubernetesクラスタのすべてのリソースにアクセスできます。

kind: github
version: v3
metadata:name: github
spec:client_id:<client-id>
  client_secret:<client-secret>
  display: Github
  redirect_url: https://teleport.example.com:3080/v1/webapi/github/callback
  teams_to_logins:- organization:<your-organization>
      team:<your-team>
      logins:- root
      kubernetes_groups:["system:masters"]

TeleportのPodに入り、github.yamlの登録をおこないます。

$ kubectl -n teleport exec -it teleport-xxx bash
$ tctl create github.yaml
authentication connector "github" has been created

ログイン

ログインの前に、Teleportにアクセスするためのアドレスを、下記のように/etc/hostsに登録しておきます。 本来はDNSで名前解決できるようにすべきですが、今回は/etc/hostsの編集でごまかします。

127.0.0.1 teleport.example.com

次にログインをおこないます。

TeleportのDownloadページからバイナリをダウンロードし、 tshコマンドが利用できるようにインストールをおこなってください。

下記のコマンドを実行するとブラウザが開き、GitHubの認証画面が開きます。 ログインに利用するOrganizationを選択してください。

なお今回はself signedな証明書なので--insecureオプションを付与してますが、Let's Encryptの証明書を使うならこのオプションは不要です。

$ tsh login --insecure --proxy=teleport.example.com:3080 --auth=github

statusを確認してみます。証明書の有効期限はデフォルトで12時間に設定されています。

$ tsh status
> Profile URL:  https://teleport.example.com:3080
  Logged in as: xxxx
  Cluster:      teleport.example.com
  Roles:        admin*
  Logins:       root
  Valid until:  2019-07-30 03:58:06 +0900 JST [valid for 11h49m0s]
  Extensions:   permit-agent-forwarding, permit-port-forwarding, permit-pty


* RBAC is only available in Teleport Enterprise
  https://gravitational.com/teleport/docs/enterprise

ログインに成功すると、kubeconfigにTeleport用のcontextが追加されています。 teleport.example.comという名前のContextがあればOKです。

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://teleport.example.com:3026
  name: teleport.example.com
contexts:
- context:
    cluster: teleport.example.com
    user: teleport.example.com
  name: teleport.example.com
current-context: kubernetes-admin@teleport-demo
kind: Config
preferences: {}
users:
- name: teleport.example.com
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

現在のContextを確認し、必要があれば切り替えます。

$ kubectl config current-context
teleport.example.com
$ kubectl config use-context teleport.example.com

kubectlを叩いてみましょう。現在はsystem:mastersの権限なので、すべてのリソースにアクセスができるはずです。 なお今回はself signedな証明書なので--insecure-skip-tls-verify=trueオプションを付与しています。

$ kubectl --insecure-skip-tls-verify=true get pods -A

kubectl execを実行して、適当にコマンドを叩いてみてください。 ブラウザでhttps://teleport.example.com:3080/webを開いてログインすると、 kubectl execで実行した内容を動画のようにリプレイ表示することができます。

Recording kubectl session

なお本物の動画ではないので、一時停止して、実行したコマンドやその結果をテキストとしてコピーすることもできます。

権限設定

次にアクセス権の制限されたKubernetesグループを定義してみましょう。 下記のようなファイルを作成し、rbac.yamlという名前で保存します。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:name: guest-role
rules:- apiGroups:- ""resources:- pods
      - deployments
      - services
    verbs:- get
      - list
---apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: guest-role-binding
roleRef:apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: guest-role
subjects:- kind: Group
    name: guest
    apiGroup: rbac.authorization.k8s.io

下記のコマンドで適用します。

$ kubectl apply -f rbac.yaml

先ほどのgithub.yamlkubernetes_groupsを書き換えます。

kind: github
version: v3
metadata:name: github
spec:client_id:<client-id>
  client_secret:<client-secret>
  display: Github
  redirect_url: https://teleport.example.com:3080/v1/webapi/github/callback
  teams_to_logins:- organization:<your-organization>
      team:<your-team>
      logins:- root
      kubernetes_groups:["guest"]

TeleportのPodに入り、github.yamlを再登録します。

$ kubectl -n teleport exec -it teleport-xxx bash
$ tctl rm github/github
github connector "github" has been deleted
$ tctl create github.yaml
authentication connector "github" has been created

再度tshでログインし直して、kubectlを実行してみましょう。

podserviceをgetすることはできますが、それ以外のリソースはアクセスできないことが確認できます。

$ kubectl --insecure-skip-tls-verify=true get configmaps
Error from server (Forbidden): configmaps is forbidden: User "xxxx" cannot list resource "configmaps" in API group "" in the namespace "default"

まとめ

今回は、Teleportを利用してKubernetesクラスタへのユーザーアクセスを管理する方法を紹介しました。 この方法を利用することで、GitHubのTeamごとにKubernetesクラスタへのアクセス権限を設定することが できるようになり、非常に簡単にユーザー管理をおこなうことができるようになりました。

本記事がKubernetesのユーザー管理の参考になれば幸いです。

新卒デザイナー US出張体験記

$
0
0

こんにちはサイボウズの新卒デザイナーの王子田(おーじ)です。

3月に大学を卒業して、UX/UIデザイナーとしてサイボウズに入社しました。新卒1年目ですが、7月にサンフランシスコに行き、リサーチをしてきました。

今回は人生初の海外出張で感じたこと、そして現地で行ったことについて書いていきたいと思います。 現地、日本のメンバーが盛り上がって話している様子

何をしたのか?

現在サイボウズは米国でKintoneを広げるため、チーム一丸となって活動しています。今回の出張の目的はポータルカスタマイズ機能の活用方法のリサーチと、使い方の紹介です。 世界中のユーザーにポータルカスタマイズを使ってもらうためのヒントを探してきました。

リサーチ

今回のヒアリングは僕とデザイナーの篠原でタッグを組み実施しました。

カスタマーサクセスチーム

ポータルカスタマイズ機能がどのように活用できそうかヒアリングをしました。僕がヒアリングしながら篠原がその場でペーパープロトタイプを作っていき、それをもとにディスカッションをして共通理解を深めていくというスタイルを取りました。ヒアリング後はfigmaプロトタイプをブラッシュアップしました。

スピード感を持ってプロトタイプを作ることが出来たので、ディスカッションを深めることができて良かったです。アイデアがすぐ形になることに現地のメンバーも非常に喜んでいました。 現在はそこで得たアイデアを元にポータルを開発している段階です。

ヒアリング中に作ったペーパープロトタイプ
ヒアリングをしながら作ったペーパープロトタイプ
画面で見れる詳細なプロトタイプ
画面で見れる詳細なプロトタイプ

SE(セールスエンジニア)

今後、お客様により価値の高いポータルを提案するために、SEチームとデザイン&リサーチチームがどのように協業していけるか話し合いました。 ヒアリングをした結果、USメンバーにカスタマイズポータルの機能が十分に伝え切れていないことがわかったので、今後は密にコミュケーションを取って、新しいポータルの可能性を探求して行きたいと思います。

NPO

NPOのお客様にどのようなサポートを提供できそうかNPOのセールス担当と話し合いました。 ヒアリングをしたところ、その団体の特徴が一目でわかるようなポータルを提供すると、お客様に喜ばれそうだということがわかりました。こちらも現在開発中です。

ポータルカスタマイズ勉強会

USメンバーに機能やツールの使い方を紹介しました。 ハンズオンセッションの際には質問も多く出てきて、現地メンバーはとても興味を持ってくれている様子でした。ノーコードでカスタマイズできるような要望が出たりとこれからに繋がるとても良い時間になりました。 勉強会でプレゼンをしている写真

メンバーとの交流

現地のメンバーと一緒に過ごす時間が多く、仕事以外の話で盛り上がったりとチームの絆を深める良い時間を過ごすことができました。 普段文字だけでしかコミュニケーションしない現地メンバーと実際に会って話すことはお互いの距離を縮め、同じゴールに向かっているチームのメンバーとして強い意識を持つことができました。 現地のメンバーとランチを食べている写真

まとめ

本当に良い経験をさせてもらいました。 僕はまだ入社したばかりだったのですが、責任のある新機能のプレゼンをする機会をいただきました。 サイボウズのデザイン&リサーチチームが大切にしているプランニング、リサーチ、プロトタイピングという流れを肌で体験することができ、UXを中心としたデザインプロセスを実際に行うことができました。

そして何より現地のメンバーと繋がることによってモチベーションがすごく上がりました。 サイボウズのミッションであるチームワーク溢れる社会を作るという言葉にさらに共感し、それに向けて自分も貢献して行きたいと思います。

デザインチームのメンバーが空港でクラムチャウダーを食べている様子
帰りはサンフランシスコ空港で名物のクラムチャウダーを食べました!

海外で仕事がしたいリサーチャー募集中!

現在デザイン&リサーチチームは「海外で仕事がしたい!」というリサーチャーを大募集中です。興味のある方のご応募をお待ちしております!

cybozu.co.jp

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

$
0
0

こんにちは、フロントエンドエキスパートチームです。 今回は私たちフロントエンドエキスパートチームの紹介をします。

フロントエンドエキスパートチームとは

サイボウズの技術領域一覧
サイボウズの技術領域とフロントエンドエキスパートチームの立ち位置

まずサイボウズのWebフロントエンドについて触れておくと、以下の特徴を持っています。

  • プロダクトにとって、Webフロントエンドが非常に重要
    • B2Bのプロダクトを開発しているので、WebおよびPCでのユーザー利用率が高い
    • JavaScriptで製品をカスタマイズするためのAPIやSDKを提供している
  • 継続的な開発が必要で、Webフロントエンドの変化に追従していく必要がある
    • 大規模なWebアプリケーションが多く、プロダクトライフサイクルが非常に長い
  • プロダクトチームにWebフロントエンドがいるわけではない
    • プロダクトチームのエンジニアがWebフロントエンドからバックエンドまで一貫して担当する体制

そうした背景から、プロダクトを横断してWebフロントエンド分野に関する問題の解決を行なうチームが必要になっています。 その役割を担うのが「フロントエンドエキスパートチーム」です。

フロントエンドエキスパートが主にやっている業務内容としてはこのようなものがあります。

  • 各プロダクト(ex: kintone, Garoon)が抱える問題を探求して解決
  • パフォーマンスやテストなどアプリケーションの品質の向上のサポート
  • 最新技術をキャッチアップしてプロダクトでの活用を探求
  • 社内でWebフロントエンドに関する勉強会などを開催
  • サイボウズで管理しているOSSの開発やメンテナンス

などなど、いろいろありますが一言でいうと「サイボウズのWebフロントエンドを最高にする」ために活動しているチームです!

メンバー

チームメンバーで撮影している様子
チームメンバーで記念撮影

フロントエンドエキスパートチームは、現在5人所属しており、東京に4人、福岡に1人居ます。そのうち2人(外松、山崎)はkintoneチームを兼務しており、週2日フロントエンドエキスパートチームの業務を行っています。

メンバーを一人ひとり、軽く紹介します。

  • 穴井
  • 小林
  • 外松
  • sakito
  • 山崎

穴井

  • Twitter: @pirosikick
  • 出身:大分県
  • 得意なこと、好きなこと
    • JavaScriptが好きで得意です
    • 最近はwebpackのソースコードを読んでいます
  • 入社:2019/05
  • ひとこと
    • wework、ビールが飲めて、最高です
  • 拠点:福岡
  • 登壇資料一覧

小林

sakito

  • Twitter: @__sakito__
  • 出身: 愛媛
  • 得意なこと、好きなこと:
    • React周り,Gatsby.js,webpackが好き
    • 勉強会やカンファレンスを運営するのも好きでよく居るので声かけてください!
  • 入社した年月: 2019/6
  • ひとこと
    • カープがすき!!!!!
  • 拠点: 東京
  • 登壇資料一覧

外松

  • Twitter: @toshi__toma
  • 出身: 石川県金沢市
  • 得意なこと、好きなこと:
    • WebフロントエンドはReactから入りました!とにかく新しいモノが好きです。
    • 最近はDXが上がる系のツール導入が楽しいです
  • 入社した年月: 2017年に新卒エンジニアとして入社
  • ひとこと
    • 魚が美味しい金沢出身ですが、魚介類全般食べられません(泣)
  • 拠点: 東京
  • 登壇資料一覧

山﨑

  • SNS: @zaki___yama
  • 出身: 北海道
  • 得意なこと、好きなこと
    • もくもくと技術調査とかするの好きです
  • 入社した年月: 2018/12
  • ひとこと
    • 🍺🥰
  • 拠点: 東京
  • 登壇資料一覧

フロントエンドエキスパートチーム誕生の経緯

@koba04が2018年はまだ1人だった様子
2018年はまだ@koba04だけだった

フロントエンドエキスパートチームは2017年10月に誕生しました。
サイボウズでは、各プロダクトチームのエンジニアはサーバーサイドとWebフロントエンドのどちらも書くというチーム構成になっています。
これには、サーバーからWebフロントエンド全体を見た最適な設計を可能にしたり柔軟なチーム構成が可能になるなどのメリットがあります。 その反面、分野に特化した探求などはカバーする範囲が広いため難しくなるという側面もあります。

サイボウズではすでにモバイルチームや生産性向上チームなど、プロダクトチームを支援するためのチームが存在しました。
その中で、Webフロントエンドはサイボウズのプロダクトにおいても重要な位置付けであり、専門性を追求して支援するチームが必要という問題意識から、 @koba04が入社するタイミングでチームが作成されました。

そのため、最初の約1年は1人チームとして各プロダクトが抱える問題を解決したり、勉強会の開催などを行っていました。
そんな中、サイボウズのWebフロントエンドをもっとよくしていきたいという思いを持ったkintoneチームの @zaki___yama@toshi__tomaが兼務という形で参加してくれることになり、より多くの問題に対して取り組めるようになりました。

さらに5月、6月には @pirosikick@__sakito__が専任メンバーとして加わったのでチームとして動き出したところです。

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

ここからはフロントエンドエキスパートチームが普段どのような活動をしているかを紹介します。

  • 朝会
    朝会では、前日行なったこと、今日行うことを軽く共有します。
    共有が終わったら個人的な出来事や気になるWebフロントエンド記事をネタに雑談をしています。

  • フロントエンドエキスパートチームでのモブプロ
    サービスチームでは解決できない課題やOSSについて、フロントエンドエキスパートチームメンバーで毎日2時間ほどモブプロで開発を行います。
    開発が完了するとその場でPRを出し、サービスチームに関与する場合はレビューをもらい、フロントエンドエキスパートチームだけで判断できる開発の場合はマージまで行います。

モブプロをしている様子
各拠点メンバーがオンラインに入りモブプロをしています

  • サービスチームとモブプロ
    フロントエンドエキスパートチームが開発したものやサービスチームと一緒に考えた方がいいと思った場合は、一緒にモブプロを行います。
    直近だとQAチームとReactのテストの書き方について、サービスチームのWebフロントエンド設計などWebフロントエンドに関与すること全般の関わります。

  • OSS Contribution
    利用しているOSSで問題や改善点が見つかった場合は、Pull RequestやIssueの登録を行います。 各メンバーのOSS Contributionはkintoneのアプリ上で見られるようにしています。

OSS貢献数を可視化したグラフ
フロントエンドエキスパートチームが月毎のOSS貢献数を可視化したグラフです

  • SpeedCurveメンテ&チェック
    月に1度、SpeedCurveを見ながらサービスのパフォーマンスをチェックします。
    現状は最適な指標を模索している最中です。

  • 公開しているOSSやnpmパッケージのメンテナンス/リリース
    kintone用のnpmパッケージ全体で使用するnpmパッケージをOSSとして公開しているので、セキュリティの脆弱性やPRが来た際には随時対応をしています。 また、依存パッケージの更新をRenovateで随時行なっており、月に1度ほど変更があったnpmパッケージをリリースしています。
    こちらについては、別途記事で紹介する予定です。

  • 全体に向けてWebフロントエンド関連の情報を共有
    Webフロントエンドに関する情報をkintone上で随時共有しており、フロントエンドエキスパートチーム以外にも全社員が見ることのできる場なので、誰かが気になった情報はその場で議論しています。

    kintone上のフロントエンドエキスパートチームのスペース
    kintone上にフロントエンドエキスパートチームのスペースがあり、ここで情報が交換されています

  • フロントエンドランチ
    毎週火曜日にランチを食べながらWebフロントエンドに関する記事や、ブラウザーやライブラリの更新情報などを見ていく会です。
    参加メンバーは自由なので、さまざまなサービスの人たちでワイワイやっています。事前にみんなが気になった情報をkintone上に共有し、フロントエンドエキスパートチームが持ち回りで進行を行います。

  • 振り返り会
    毎週金曜日に、その週に対応したタスクの振り返りを実施しています。モブプログラミングで全員が取り組んでいるタスク以外にも、個人で探求しているタスクの進捗のシェアしたり、チームの今後について話し合ったりしています。

  • 勉強会の主催や登壇
    勉強会への登壇や主催運営も業務として行います。 登壇資料の作成や主催を行う場合の調整も業務中に行います。

  • もくもく会
    月に1度weworkでもくもく開発します。 内容は各自が普段の業務では行なっていないことなどをネタに探求を行います、そして最後に全員で共有します。
    9月は福岡にいるメンバーへチームの全員で会いに行き、福岡のweworkで行う予定です。

乾杯している様子
もくもく開発が終わったら乾杯をして成果報告会!

これらの業務は複数拠点間でコミュニケーションロスがないように、各自の席でZoomを使用し、オンラインで行なっています。

フロントエンドエキスパートチームの採用

Webフロントエンド技術が好きな方をお待ちしております!
福岡、広島、大阪など働く場所は関係ありません!どこでも歓迎です!

詳しい採用情報はこちら

安全なKubernetesクラスタのつくりかた 〜ポリシー編〜

$
0
0

こんにちは、Necoプロジェクトの池添(@zoetro)です。

今回は、安全なKubernetesクラスタを構築するために、我々がどのようなポリシーを適用しているのかを紹介したいと思います。

Kubernetesクラスタのセキュリティ対策

安全なKubernetesクラスタを構築するためには、非常にたくさんの項目について検討しなければなりません。 ざっと挙げてみただけでも以下のような項目があります。(詳細は Kubernetesの公式ガイドを参照)

  • Role-Based Access Control (RBAC)
  • ネットワークアクセスの制御(Network Policy)
  • コンテナの権限(Pod Security Policy)
  • 通信の暗号化
  • Secretの暗号化
  • 信頼できるコンテナイメージの利用
  • 安全なコンテナランタイムの利用
  • ユーザー/グループの管理
  • API ServerのAudit Log
  • KubeletのAuthorization
  • サービスメッシュによるトラフィック制御

これらの項目はそれぞれどのように設定すべきでしょうか?

それを決めるためには、どのような脅威から自分たちのシステムを守りたいのかという基本方針を明確にしておくことが重要です。 例えば基本方針には関係のないセキュリティ対策を実施しても、コストやオーバーヘッドが増えるだけで意味がありません。

今回は Pod Security Policy と Network Policy そして Open Policy Agent に焦点を当てたいと思います。 これらのポリシーを設定することでコンテナができることを制限することができます。 それにより悪意のあるユーザーの攻撃や通常のユーザーの誤りによってコンテナが管理者の意図しない危険なことをしようとした場合に、被害を最小限に食い止めることが可能になります。

ポリシー適用例

本記事では、我々が適用しているポリシーの一例を紹介します。 なお、ここで紹介している例はサイボウズの環境に適したポリシーなので、あくまでも参考として見ていただければと思います。

Pod Security Policy (PSP)

Pod Security Policyとは、 Kubernetes クラスタ上で動作する Pod に与える権限を制御するための機能です。 例えば、以下のような制限を与えることができます。

  • privilegedコンテナの利用禁止
  • Linux Capability による制限
  • 利用可能な Volume の種類を制限
  • hostNetworkの利用禁止
  • rootでのコンテナの実行禁止
  • ルートファイルシステムの書き込み禁止

Necoではクラスタ全体としてはほとんど何もできないようにしておいて、個々のアプリについて必要最小限の権限を与えるようにしています。

まずは、もっとも厳しい制限として下記のようなポリシーを用意します。

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:name: restricted
spec: # 特権コンテナの実行を禁止privileged:false # 親プロセスよりも強い権限を許可しないallowPrivilegeEscalation:false # すべてのCapabilityをDROPするrequiredDropCapabilities:- ALL
  # 利用可能なVolumeを制限volumes:- 'configMap'- 'emptyDir'- 'projected'- 'secret'- 'downwardAPI'- 'persistentVolumeClaim' # ホストとネットワーク、プロセス間通信、プロセスIDの共有を許可しないhostNetwork:falsehostIPC:falsehostPID:false # rootでの実行を許可しないrunAsUser:rule:'MustRunAsNonRoot' # 任意のSELinuxのラベルを利用可能seLinux:rule:'RunAsAny' # コンテナが追加可能なグループIDの範囲を指定 (rootを禁止)supplementalGroups:rule:'MustRunAs'ranges:- min:1max:65535 # ボリュームに適用されるグループIDの範囲を指定 (rootを禁止)fsGroup:rule:'MustRunAs'ranges:- min:1max:65535 # ルートファイルシステムへの書き込みを禁止readOnlyRootFilesystem:true

上記のポリシーを、すべてのユーザーとアカウントサービスに適用されるように ClusterRoleとClusterRoleBindingを用意します。

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: psp:restricted
  annotations:rbac.authorization.kubernetes.io/autoupdate:"true"rules:- apiGroups:['policy']resources:['podsecuritypolicies']verbs:['use']resourceNames:- restricted
---kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: default:psp:restricted
  annotations:rbac.authorization.kubernetes.io/autoupdate:"true"roleRef:kind: ClusterRole
  name: psp:restricted
  apiGroup: rbac.authorization.k8s.io
subjects:- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:serviceaccounts
- kind: Group
  apiGroup: rbac.authorization.k8s.io
  name: system:authenticated

このポリシーは非常に厳しい制限がかかっています。 Helm Chartで公開されているマニフェストをそのまま持ってくると、多くの場合動作しないでしょう。

例えば、ネットワークプラグインを動作させるためには hostNetwork の利用が必要になりますし、 DNSサーバーを動かすためには NET_BIND_SERVICE を許可して、53番ポートで待ち受けられるようにする必要があります。

そこで、各アプリケーションごとに制限を緩めたポリシーを用意して適用していきます。 例えば、DNS Cacheのunboundには以下のようなポリシーを適用しています。

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:name: unbound
spec:allowPrivilegeEscalation:false # 利用可能なCapabilityを追加allowedCapabilities:- NET_BIND_SERVICE
  volumes:- 'configMap'- 'emptyDir'hostNetwork:falserunAsUser:rule:'RunAsAny'seLinux:rule:'RunAsAny'supplementalGroups:rule:'RunAsAny'fsGroup:rule:'RunAsAny'readOnlyRootFilesystem:true

Network Policy

Network Policyは、Pod間の通信や Podと他のネットワークエンドポイントとの通信の可否を制御するための仕組みです。 なお、Network Policyの機能を利用するためには何らかのネットワークプラグインが必要となります。

我々は Calico Network Policyを利用しています*1

Calico Network Policy のカスタムリソースは、Kubernetes 標準のNetwork Policyと比較してより高度な機能を提供しています。 例えば、orderフィールドを利用してネットワークポリシーの適用順序を制御することができます。

Network Policyでは、Podに入ってくるトラフィックを制御するためのIngressと、Podから出ていくトラフィックを制御するためのEgressという2種類の設定があります。

Ingress

NecoのIngress ルールでは、基本的にデータセンター内の通信であれば許可し、データーセンター外からの通信は拒否しています。

Ingress Rule

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:name: ingress-cluster-allow
spec:order:9000.0types:- Ingress
  ingress:- action: Allow
      source:nets:- 10.0.0.0/8
---apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:name: ingress-all-deny
spec:order:10000.0types:- Ingress
  ingress:- action: Log
    - action: Deny

Ingress という namespace を用意し、インターネット経由でアクセスされる Pod を配置します。 L7ロードバランサーである contour には下記のようなポリシーを適用し、インターネットからのアクセスを許可します。

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:name: ingress-contour
  namespace: ingress
spec:order:1000.0selector: app.kubernetes.io/name == 'contour'types:- Ingress
  ingress:- action: Allow
      protocol: TCP
      destination:ports:- 8080- 8443

なお、他のnamespaceからのアクセスを遮断したいケースもあるでしょう。 そういう場合は、各namespaceごとに通信を受け入れない設定を適用します。

Egress

一方の Egress ルールでは、Pod から Node へのアクセスを禁止していますが、それ以外は基本的に許可しています。

Egress Rule

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:name: egress-node-deny
spec:order:1000.0types:- Egress
  egress:- action: Deny
      destination:nets:- 10.69.0.0/16
---apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:name: egress-all-allow
spec:order:10000.0types:- Egress
  egress:- action: Allow

ただし、Nodeへのアクセスであっても、kube-apiserver(6443ポート)やetcd(2379ポート), DNS Cache(53ポート)については、下記のようにアクセスを許可します。

apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:name: egress-controlplane-allow
spec:order:500.0types:- Egress
  egress:- action: Allow
      protocol: UDP
      destination:nets:- 10.69.0.0/16
        ports:- 53- action: Allow
      protocol: TCP
      destination:nets:- 10.69.0.0/16
        ports:- 53- 2379- 6443

また、インターネットにアクセス可能なPodを配置するために Internet-Egress という namespace を用意し、 Web Proxy である squid や DNS キャッシュサーバである unbound を配置しています。 この namespace から他の namespace やホストへの通信を禁止しています。

Open Policy Agent

RBACとPodSecurityPolicy, NetworkPolicyを適用することで、Podに対して様々な制限をかけることができます。 しかし、これらのポリシーだけでは以下のような細かな制限をおこなうことはできません。

  • コンテナイメージを取得するレジストリを制限したい
  • system namespaceへのアクセスを制限したい
  • 一部のクラスタリソースの操作を許可したい
  • すべてのPodにResource Limitの付与を強制したい

そこで我々はOpenPolicyAgentを利用して、より柔軟なポリシーを適用しています。

www.openpolicyagent.org

OpenPolicyAgentでは、Regoという独自の言語でポリシーを記述することができます。

例えば先程紹介したNetworkPolicyでは、orderフィールドでポリシーの適用順序を指定していました。 開発者が適用順序の早いポリシーを作成できてしまうと、本来適用したいポリシーが無視されてしまいます。

そこで以下のようなルールを用意し、orderが1,000以下のNetworkPolicyの作成や更新を禁止することで、重要なNetworkPolicyを必ず適用することが可能となります。

package kubernetes.admission

operations = {"CREATE", "UPDATE"}
system_namespaces = {"kube-system", "ingress", "internet-egress"}

deny[msg] {
    input.request.kind.kind == "NetworkPolicy"
    input.request.kind.group == "crd.projectcalico.org"
    operations[input.request.operation]
    not system_namespaces[input.request.namespace]
    input.request.object.spec.order <= 1000
    msg := "cannot create/update non-system NetworkPolicy with order <= 1000"
}

OpenPolicyAgent の活用方法については、機会があれば別途紹介したいと思います。

まとめ

Kubernetesクラスタをセキュアにするためのポリシーとして、 Pod Security Policy, Network Policy, OpenPolicyAgentの適用例を紹介しました。

なお、このポリシーを適用していればどんな環境でもセキュアで安心というわけではありません。 本記事の例を参考に、それぞれの環境に適したポリシーを適用していただければと思います。

また、Kubernetesにデプロイされているアプリケーションが増えれば増えるほど、 あとからポリシーを適用するのは非常に大変になります。 ポリシーを適用したいと考えているのであれば、早い段階での適用をおすすめします。

*1:Necoでは自作のネットワークプラグインcoilを利用していますが、coilはネットワークポリシーを実装していないため。

Puppeteerで不要なCSSを消す

$
0
0

こんにちは。フロントエンドエキスパートチームの穴井(@pirosikick)です。福岡在住で、普段は福岡のweworkで働いています。他のメンバーは皆、東京に居てリモートで仕事をしていますが、モブでわいわい開発していますし、weworkが快適すぎて、毎日楽しいです!

フロントエンドエキスパートチームでは、サイボウズの各プロダクトが抱えるWebフロントエンドの課題を解決するのが仕事の一つです。

blog.cybozu.io

最近の取り組みとして、Puppeteerで不要なCSSを消した事例を紹介します。

このブログは、6/19に福岡で開催した「Google I/O '19のWebをまとめる会」で登壇したときの内容を詳細に説明しつつ、アップデートした部分もあるので、発表見たぞ、スライド見たぞという方も見ていただけますと幸いです。

speakerdeck.com

きっかけ

とあるプロダクトのCSSをstyled-componentsに移行していたのですが、その中の一つのCSSファイルが、

  • 1万行ある
  • どこで使われているのか不明なスタイルが多くある
  • 大量のセレクタに対してスタイルを当てているルールが多くあり移行作業のコストが高い

など、多くの課題を抱えており、どうやって移行すればよいのかと頭を抱えていました。そんな時、たまたま観たGoogle I/O '19のPuppeteerに関するセッションでCSSのカバレッジを取得できることを知り、「この機能を使って不要なCSSを洗い出せば、ちょっとは楽ができるのでは?」と思ったのがきっかけです。

結果はあまり期待せずPuppeteerを使い始めたのですが、結果として1万行あったCSSファイルは7%くらいしか使われていないことが判明し、想定よりも大幅に不要なスタイルを削除できました。そのかいあって、styled-componentsへの移行作業を無事終えることができました!

ここから、Puppeteerとは何か?から、取得したカバレッジをもとにCSSを削除する方法について解説していきます。

Puppeteerとは?

Puppeteer

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol.

Puppeteerは、Chromeを操作するAPIを提供するNode.jsのライブラリです。ChromeのDev Toolsのプロトコルを介して、Chromeを操作します。使い方は簡単で、npm install puppeteerでPuppeteerをインストールし、コードを書くだけです。

const puppeteer = require("puppeteer");

(async () => {// ブラウザの起動const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // Googleに移動
  await page.goto("https://google.com");
})();

もっと詳しく知りたい場合は公式ドキュメントもよいですが、Google I/O '19のPuppeteerのセッションがわかりやすいのでおすすめです。

www.youtube.com

PuppeteerでCSSのカバレッジを取る

PuppeteerでCSSのカバレッジを収集するには、まずpage.coverage.startCSSCoverage()でカバレッジの収集を開始します。startCSSCoverage呼び出し時のオプションで、resetOnNavigationfalseにしないと、ページ遷移が発生するたびにカバレッジがリセットされてしまうので注意しましょう。その後、page.goto(...)などでブラウザを操作し、カバレッジを収集したいページを表示させます。収集したいページを表示し終えたら、page.coverage.stopCSSCoverage()でカバレッジの収集を終わります。stopCSSCoverageは、startCSSCoverage呼び出しからstopCSSCoverage呼び出しまでに収集したカバレッジを返します。

// カバレッジ収集を開始
await page.coverage.startCSSCoverage({// ページ遷移のたびにカバレッジがリセットされるのを防ぐ
  resetOnNavigation: false});

// ブラウザを操作しカバレッジを収集したいページを表示する
await page.goto('…');
…


// カバレッジ収集を終了// startCSSCoverageからstopCSSCoverageまでの間に収集したカバレッジを返すconst coverage = await page.coverage.stopCSSCoverage();

ちなみに、JavaScriptのカバレッジはpage.coverage.startJSCoverage(), page.coverage.stopJSCoverage()で同様に取得できます。

カバレッジを視覚的に確認する

収集したカバレッジを視覚的に確認するには、puppeteer-to-istanbulでカバレッジをistanbulのフォーマットで出力し、istanbulのCLIツールであるnycでレポートを出力します。

# puppeteer-to-istanbulのインストール
$ npm install --save puppeteer-to-istanbul
const puppeteer = require('puppeteer');
const ptoi = require('puppeteer-to-istanbul');

…割愛…

// カバレッジを収集const coverage = await page.coverage.stopCSSCoverage();
// カバレッジをistanbul形式に変換&.nyc_output以下に出力
ptoi.write(coverage);
# HTML形式でレポートを出力
$ npx nyc report --reporter=html

# ブラウザで表示する(macの場合)
$ open coverage/index.html

HTML形式で出力した場合、以下のようなレポートが出力されます。赤く表示されている部分がカバレッジがない部分です。

HTML形式のカバレッジレポート
HTML形式のカバレッジレポート

取ったカバレッジを元にCSSから不要な部分を削除する

page.coverage.stopCSSCoverage()が返すカバレッジ情報は以下のような構造になっています。

[{
    url: "ファイルのURL",
    text: "ファイルの中身",
    ranges: [{ start: "カバレッジ開始位置", end: "カバレッジ終了位置"},
      { start: "カバレッジ開始位置", end: "カバレッジ終了位置"},
      …
    ]},
  …
]

よって、以下のようにrangeを元にtextからカバレッジがあるコードのみ抜き出せば、使われていないコードが除外されたCSSを生成できそうな感じがします。

const coverage = await page.coverage.stopCoverage();

const coolCSS = coverage.map(({ url, text, ranges }) => {// rangesを元に使われているCSSだけを抜き出すconst css = ranges
    .map(({ start, end }) => text.substring(start, end))
    .join("");

  return{ url, css };
});

console.log("I got cool css!", coolCss);

が、この方法にはいくつか落とし穴があります。

落とし穴 ①: @font-faceにカバレッジが発生しない

Puppeteerの出力するカバレッジには、@font-face { … }のカバレッジが発生しません。よって、カバレッジからCSS中の@font-face {…}が必要なものなのか判断することができません。そのため、表示崩れを防ぐためには、@font-faceは、消さずに常に残す必要があります。

ちなみに、PuppeteerのGithub にこの挙動に関する issueが登録されていますが、開発チームから反応がなく、現時点では仕様なのかバグなのかはわかりません。

落とし穴 ②: @mediaにカバレッジが発生しない

@mediaを使うとスタイルが適応される条件を定義できます。レスポンシブ対応でお馴染みの記法です。

/* 引用元: http://www.tohoho-web.com/css/rule/media.htm *//* 幅が 767px 以下であれば */@media (max-width: 767px) {
  ...;
}/* 幅が 768px 以上であれば */@media (min-width: 768px) {
  ...;
}/* 幅が 768px以上 1200px以下であれば */@media (min-width: 768px) and (max-width: 1200px) {
  ...;
}/* カラーディスプレイまたはモノクロ印刷であれば */@media screenand (color),printand (monochrome) {
  ...;
}

Puppeteerでは、@media … { … }内のスタイルはカバレッジが発生しますが、開始の@media … {と終了の}にカバレッジが発生しません。

@media{}にカバレッジが発生しない
@media{}にカバレッジが発生しない

よって、中身のスタイルにカバレッジがある@mediaだけ消さずに残すというやや複雑な処理が必要です。

AST を使ってコードを抜き出す

このような落とし穴があるため、最初に紹介した方法(カバレッジを元にsubstring関数を使って必要なCSSだけを抜き出す)では、必要なCSSまで削除されてしまいます。また、落とし穴②を文字列ベースで解決しようとすると、なかなか複雑な処理を書く必要があります。そこでおすすめなのが、ASTを使って処理する方法です。

ASTとは?

ASTとはAbstract Syntax Treeの略で、日本語でいうと抽象構文木で、ソースコードを木構造で表現したものです。Webフロントエンドでは欠かせないツールになっているBabelやwebpack、TypeScriptなども内部でASTを利用しており、WebフロントエンドにおいてASTはだいぶ馴染み深いものになっているのではないでしょうか?CSSだとPostCSSが有名なので、PostCSSのASTを使って説明していきます。以下のCSSをPostCSSでASTに変換すると図のような木構造になります。

@media screenand (min-width: 480px) {body{background-color: lightgreen;
  }}#main{border: 1pxsolidblack;
}ulli{padding: 5px;
}

ASTのイメージ図
ASTのイメージ図

AST Explorerを使うと、ソースコードがどのようなASTに変換されるかリアルタイムかつ視覚的に確認することができます。

AST explorer
AST explorer

ASTを使ってソースコードを編集する利点は、シンタックスエラーを含むソースコードを生成する可能性がないことと、編集するロジックがシンプルになる点です。 ソースコードを文字列として扱って編集した場合、編集するロジックに誤りがあるとシンタックスエラーになるようなソースコードが生成される恐れがあります。一方、ASTに変換して編集すれば、ASTからソースコードに戻すときにエラーになるのでシンタックスエラーになるようなコードは生成されません。また、編集のロジックもシンプルになります。例えば、上記のCSSで「#mainのスタイルを消したい」という場合は、図の真ん中のRuleを木構造から削除するだけです。

PostCSSが出力するASTは、実際にはJSのオブジェクトで表現されます。ASTの木構造への追加・削除などの操作は、各オブジェクトに関数が定義されています。では、PostCSSのASTを使って、カバレッジのないスタイルを削除するコードを解説していきます。

0. PostCSSのインストール

まず、PostCSSをnpmでインストールします。

# PostCSSのインストール
# 適宜--saveや--save-devを付ける
$ npm install postcss

1. CSSからASTを生成する

CSSのソースコードからASTを生成します。postcss.parser関数にCSSを文字列で渡すと、ASTのRoot(木構造の根)を返します。ちなみに、渡したCSSにシンタックスエラーがあるとエラーを投げます。

const postcss = require("postcss");

/** * PuppeteerのCSSカバレッジから不要なスタイルを削除したCSSを生成 * * @param {Object} coverage - stopCSSCoverageが返す配列の要素 */const removeUnusedCSS = coverage => {// CSSからASTを生成const root = postcss.parser(coverage.text);
  …
};

2. ASTを探索して削除対象のノードを見つける

次に、rootに生えているwalk関数を使って木構造を探索し、削除対象のノードを見つけます。

// ASTの探索
root.walk(node => {// 削除対象か?if (isNodeUnneeded(node)) {// 削除対象ならASTから削除する
      node.remove();
  }});

root.walk()のイメージ図
root.walk()のイメージ図

3. ノードが削除対象か判定する

ノードが削除対象か判定するisNodeUnneeded関数を実装します。ノードを削除するかは以下のような条件です。コメントを削除するかは好みですが、今回は「さらなる高みへ:使われていないセレクタ記述も除去する」の章で必要になるのでそのまま残します。PuppeteerのCSSのカバレッジはルール単位にしか発生しないので、Declarationも削除対象とせず、無視します。その他のノードに関しては、カバレッジの有無を判断して決めます。

  • 以下のノードは削除しない
    • Root
    • @font-face(落とし穴①)
    • コメント
    • Declaration
  • その他のノードは、カバレッジがあれば削除しない

ノードの種類はnode.typeで取得でき、Rootの場合は"root"、 コメントの場合は"comment"という感じで文字列が入っています。ノードが@font-faceかどうかは、node.type"atrule"で、node.name"font-face"かで判定します。

// ノードが削除対象か判定const isNodeUnneeded = (node, coverage) => {// Root, Comment, Declarationは削除しないif (['root', 'comment', 'decl'].includes(node.type)) {returnfalse;
  }// @font-faceは削除しないif (node.type === 'atrule'&& node.name === 'font-face') {returnfalse;
  }// その他:カバレッジが無ければ削除};

ノードのカバレッジ有無は、ノードのソースコード上の位置がカバレッジの範囲に存在するかで判定します。ノードのソースコード上の位置はnode.sourceから取得します(①)。node.source.startが開始位置、node.source.endが終了位置です。 node.sourceは行番号・列番号、coverage.rangesは文字列位置になっており、そのまま比較できないため、node.sourceを文字列位置に変換します(②)。 あとは、node.sourceと範囲が被っている要素がcoverage.ranges内にあるか探します(③)。isNodeUnneeded関数なので、カバレッジが無ければtrueを返しています。

// その他:カバレッジが無ければ削除// ①ノードのソース上での開始・終了位置(行列番号)const{ start, end } = node.source;
  // ②行列番号を文字列位置(0 ~ ソースコード文字列.length - 1)に変換const startIndex = lineColumnToIndex(coverage, start.line, start.column);
  const endIndex = lineColumnToIndex(coverage, end.line, end.column);

  // ③ノードのソースコード上の範囲を含むカバレッジを探すconst covered = coverage.ranges.find(
    range => !(startIndex => range.end || endIndex < range.start)
  );

  // カバレッジが見つかれなければ、trueを返すreturntypeof covered === "undefined"}

4. ASTからソースコードを生成する

1~3の処理で、ASTから不要なノードがなくなりました。最後に処理後のASTからCSSを生成します。ASTからCSSを生成するには、root.toString()を実行します。

// 不要なノードが削除されたASTからCSSを生成
return root.toString();

ちなみに、Root以外のノードもtoString()を実行すると、そのノードが表すソースコードを返します。

コードの完全版は以下に置いています。割愛したlineColumnToIndex関数の実装など、見てみたい場合はこちらからどうぞ。

removeUnusedCSS.js

さらなる高みへ:使われていないセレクタ記述も除去する

PuppeteerのCSSのカバレッジは、CSSのルールごとに発生します。

/* ルール = セレクタ(h1)+宣言ブロック(color: red;…)*/h1{color: red;
    font-size: 12px;
}

例えば、以下のようなh1〜h6にスタイルを宣言しているルールがあるときに、実際にはh1しか使われていない場合でも、ルール全体にカバレッジが発生します。

/* 実際にはh1しか使われていない場合でも、ルール全体にカバレッジが発生 */h1,h2,h3,h4,h5,h6{
  ...
}

使われていないセレクタがあってもルールごとにカバレッジが発生する
使われていないセレクタがあってもルールごとにカバレッジが発生する

今回、対応を行ったCSSには以下のような多くのセレクタを指定しているルールがいくつもありました。このままではカバレッジをもとにCSSを削除した後にstyled-componentsにスタイルを移すとき、セレクタが使われているか精査する手間が発生し大変です。

/* 実際にあったセレクタがいっぱいあるルールの一例 */.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{…;
}

使われていないセレクタも削除できるように一工夫しましたので、紹介します。

1. ルールを各セレクタごとに定義する

Puppeteerがルールごとにしかカバレッジを出せない部分は変更できないので、ルールに複数のセレクタがある場合に各セレクタごとにルールを定義するようにCSSを変換しました。言葉だとわかりづらいと思うので、変換前後で説明すると、変換前がさきほどのh1〜h6に対するルールが一つある場合、h1~h6それぞれにルールを定義するようにCSSを変換しました。h1〜h6の場合、同じ宣言ブロックをもつルールが6個できるということです。また、手順3で必要なので、変換後のコードの前後の行に目印として/* flatten-start *//* flatten-end */を付けます。

/* 変換前 */h1,h2,h3,h4,h5,h6{}/* 変換後 *//* flatten-start */h1{}h2{}h3,h4, h5は割愛…
h6{}/* flatten-end */

こちらの作業もpostcssを使って、実装しました。

/** * flatten.js * * ## before * a, b, c { … } * * ## after: * a { … } * b { … } * c { … } */const fs = require("fs");
const postcss = require("postcss");

const filePath = process.argv[2];
if (typeof filePath !== "string") {
  console.log("USAGE: node flatten.js path");
  process.exit(1);
}const css = fs.readFileSync(filePath).toString();
const root = postcss.parse(css);

root.walkRules(node => {// セレクタが一つしかないルールは無視if (node.selectors.length === 1) {return;
  }// ルールのセレクタごとにルールを作るconst rules = node.selectors.map(selector =>
    postcss.rule({ selector, nodes: node.nodes })
  );
  // 作成したルールと目印コメントを挿入
  node.after([
    postcss.comment({ text: "flatten-start"}),
    ...rules,
    postcss.comment({ text: "flatten-end"})
  ]);
  // 元のルールを削除
  node.remove();
});

console.log(root.toString());
# 実行
$ node flatten.js app.css > flatten.app.css

2. 1のCSSのカバレッジを出し、不要なCSSを削除

1の作業で生成したCSSでカバレッジを取ると、使われているセレクタ・使われていないセレクタが分かります。h1, h2, h3, h4, h5, h6 {…}のようなルールで、実際はh1しか使われていなかった場合、以下のようなカバレッジになります。

flatten.js実行後のCSSで取ったカバレッジ
flatten.js実行後のCSSで取ったカバレッジ

取ったカバレッジを元にCSSを削除すると、使われているセレクタのルールだけが残ります。

3. セレクタごとに分解したルールを元に戻す

1で本来一つのルールがセレクタごとに分解された状態になっているので、元に戻します。1で付けた目印(/* flatten-start *//* flatten-end */)の間に定義しているルールを一つにまとめます。

/** * deflatten.js */// …ファイルからCSSを文字列で読み込む処理はflatten.jsと全く同じなので割愛…const root = postcss.parse(css);

// コメントのノードを探索
root.walkComments(node => {// /* flatten-start */if (node.text.match(/flatten-start/)) {const selectors = [];
    let nodes;
    let current = node.next();
    // /* flatten-end */を見つけるまで探索while (!(current.type === "comment"&& current.text.match(/flatten-end/))) {// セレクタと宣言ブロックを収集
      selectors.push(current.selector);
      nodes = current.nodes;

      const prev = current;
      current = current.next();
      prev.remove(); // 分解したルールは削除}if (selectors.length) {// セレクタをまとめたルールを作成・挿入
      node.after(postcss.rule({ selectors, nodes }));
    }// 目印コメントは削除
    node.remove();
    current.remove();
  }});

console.log(root.toString());

この作業を行った後の差分の一部のスクリーンショットです。多くのセレクタがあるルールでも実際に利用している2~3個だったりして、多くのセレクタを削除できました。

実際の対応前後の差分
実際の対応前後の差分

おわり

Puppeteerのカバレッジ収集機能を使って、CSSを削除する事例を紹介しました。今回はページの表示パターンが少ないプロダクトだったので楽でしたが、ページ数・表示パターンが多い場合は特定のページに絞ってカバレッジを取るとうまくいきそうな予感がします。 また、Puppeteerのカバレッジ収集機能はGoogle I/O '19の動画を観て知ったので、これからもチームでWebフロントエンドの探究活動を行いつつ、プロダクトをどんどん改善していきたいです。

PR

9月26日(木)に福岡でCybozu Meetupを開催します。フロントエンドエキスパートチームはもちろん、生産性向上チームや、Kintoneなどのプロダクトチームも福岡に集結します。イベントページは8月中にサイボウズのconnpassにて公開予定です。福岡のエンジニアの皆さんとわいわい技術の話ができるのを楽しみにしております!

cybozu.connpass.com


広島にもオフィスができました

$
0
0

こんにちは。10年ぶりに広島にUターンした組織運営チームの水戸です。 好きな景色は電車の中から見るマツダスタジアムのグラウンドです。透明性が高いって良いですよね。

2019年4月に広島オフィスができましたのでご紹介します。

広島オフィス

広島オフィスはリージャス広島駅前センターというレンタルオフィスにあります。 オフィス探しで重視したのは多くの人が通いやすい立地・エンジニアが働きやすい環境の2点でした。 幸運にもJR広島駅から徒歩4分とアクセスの良いところに、できたばかりの良いビルを見つけることができました。

広島駅近くなので普段の通勤はもちろん仕事終わりのカープ・サンフレッチェ観戦にも便利です。 働きはじめて4ヶ月経ちましたがオフィスは綺麗で快適で、ビルは再開発が進むエリアにあるため比較的静かながらもお店には困らないという期待通りの良い環境です。 お好み焼き、汁なし担々麺、尾道ラーメンを食べられる店がいろいろあって、広島帰ってきたなと実感します。

オフィスのあるGRANODE広島。マツダスタジアムも徒歩圏内。
オフィスのあるGRANODE広島。マツダスタジアムも徒歩圏内。

リモートチーム、リモートモブ

サイボウズではリモートチームが増えており東京オフィス以外の場所で働くメンバーも珍しくありません。 広島オフィスで働く3名のエンジニアも全員別々のリモートチームに所属しています。 リモートチームの開発スタイルはチームによって様々ですが、スクラムやモブプログラミングを採り入れているチームが増えています。
拠点をまたいだリモートスクラム・リモートモブプロ実践事例

広島オフィスのメンバーも日常的に遠隔地のメンバーとモブ作業しています。 モブ作業ではチームメンバーと口頭でコミュニケーションするため、場所による情報格差が起こりにくく知識の伝達も進みやすいなどリモートチームと相性が良い面があります。 特にリモートチームに新しく加わる人にとっては、質問しやすい・孤独を感じにくいなどチーム加入時のメリットが大きそうです。

働く場所の自由化と転勤

今年は広島以外にも福岡や横浜にオフィスができ、さらにはオフィスのない岐阜に引っ越したデザイナーもいて働く場所の自由化が進んでいます。 リモートチームが増えているため「この地方だとこの製品の開発しか出来ない」といった制約もどんどんなくなってきていて、スキルセットや興味ある技術にあったチームに入りやすくなってます。

また、サイボウズの社長の青野は従業員の同意がない転勤に反対しています。
従業員の同意がない転勤を禁止してほしいです──サイボウズ青野慶久、連合の会長に働き方について意見してみた

もちろん私も希望して転勤しており、広島に移住しないと困るような事情もなく住みたい広島に住むことができました。 こちらのブログにあるように、どこにいても仕事できる環境に投資してもらえていて日々環境の改善も進んでいることを実感しており仕事面の不安もありませんでした。 広島に移住できてすごく幸せなので、広島に限らず住みたい場所に住んでやりたい仕事ができる人が増えると良いなと思っています。

最後に

どこにいても仕事できる環境になってきているため広島に閉じない仕事をこれからもやっていくつもりですが、広島にいるからこそやりやすいこともあります。 その一つが広島オフィス立ち上げです。広島でエンジニアが働きやすい環境を作って採用につなげたり、広島のエンジニアコミュニティに貢献していきたいと考えています。 広島で働く選択肢を増やしたり広島のコミュニティが更に盛り上がることで、広島がエンジニアにとって今よりもっと魅力的な街になると良いなと思ってます。 その結果、「広島が好きで住み続けたいけど仕事がないから他の地方に行く」とか「広島に移住したいけど仕事やキャリアが不安で移住できない」みたいなエンジニアを一人でも減らせると嬉しいです。

広島オフィスで募集中のポジションはこちらです。 現在、Webアプリケーションエンジニア(kintone)、Webアプリケーションエンジニア(PHP)、フロントエンドエンジニア、モバイルエンジニア、生産性向上エンジニアを募集中です。 どのチームも広島に閉じているわけではなく複数拠点にまたがるリモートチームで仕事することになります。

広島で働きたいエンジニアはもちろん、具体的な計画はないけど将来東京以外への移住に興味あるエンジニアお待ちしてます!

cybozu.com の新データセンター「Neco」が稼働開始

$
0
0

こんにちは、Necoプロジェクトの責任者 ymmtです。 Neco は、2018 年 1 月に開始した cybozu.comのデータセンターを一から刷新するプロジェクトです。

今回は Neco の成果が結実した新データセンターが稼働開始したことをお知らせします。 あわせて、現時点までの活動内容・システムの概要・今後の予定もお伝えいたします。

Neco プロジェクトについて

サイボウズの主力クラウドサービスである cybozu.comは自社で構築したデータセンターにて2011年に提供開始しました。 以来順調に業容を拡大し、現在は 30,000 社以上のお客様にご契約いただいております。

一方で、当初 100 台に満たない台数のサーバー向けに設計されたシステムは、今後の規模の拡大が困難という課題を抱えるに至りました。 今後もサービスの成長が確実視される(※グラフ1)ことを背景に、スケーラブルな新データセンターを作る Neco プロジェクトを 2018 年初に開始しました。

f:id:cybozuinsideout:20190902154926p:plain
グラフ1

Neco においては、データセンターのラック構成やネットワークアーキテクチャに至る最下層から見直していますが、Kubernetesとその周辺技術を中心に据えたことが一番の特徴です。Neco のネットワークについては以下の記事で詳しく解説しています。

blog.cybozu.io

成果の紹介

Neco の成果物はそのほぼすべてがオープンソースソフトウェアとして公開されています。 主要な成果を紹介いたします。

高機能ネットワークブートサーバー sabakan

sabakan は大量のサーバーをネットワークブート方式で管理できる高機能ブートサーバーです。 以下の機能を備えています。

  • UEFI HTTP Boot / iPXE Boot
  • Ignition設定ファイルのテンプレート
  • 起動時にダウンロードできるアセットの管理機能(ファイルサーバー)
  • リレーにも対応した DHCP サーバー機能
  • IP アドレス自動管理
  • サーバー台帳管理
  • サーバー状態管理
  • ストレージ暗号化支援機能
  • 容易な冗長化

github.com

Kubernetes 自動運用システム CKE

CKE は Kubernetes クラスタのインストールだけでなく、運用中のサーバー故障に対応して自動かつ安全に入れ替えるなどの高度な運用自動化機能を備えています。 CloudNative Days Tokyo 2019のセッションで紹介しました。

speakerdeck.com

Coil

Neco のネットワークはサーバーが BGP スピーカとなることで自在に経路制御をする仕組みです。 Coil はその要となるサイボウズ製の Kubernetes 用ネットワークプラグインです。

github.com

TopoLVM

Kubernetes 上でストレージやデータベースを取り扱う仕組みは現在進行形で成熟しつつあります。 オンプレミスでは、ストレージは最終的に各サーバーのローカルストレージとなるため、LVMを Kubernetes 上から利用できるようにしたものが TopoLVM です。類似のストレージプラグインの中では、完成度が高いと自負しています。

github.com

2019年9月時点のシステム

f:id:cybozuinsideout:20190902161356p:plain
Neco システム概要図

上記の図が現時点の Neco データセンターのシステム概要となります。ピンクの部分が Neco で新規に開発したもの、青の部分が Kubernetes と周辺のオープンソースソフトウェアを活用して構築したものとなります。以下、現時点で備えている機能を列挙します。

認証プロキシ: Teleport

Gravitational 社が提供している Teleportというソフトウェアを使って Neco データセンターにアクセスするユーザーの認証と証跡を管理しています。SSH に加えて、Kubernetes の CLI である kubectlの認証も管理できる優れものです。以下の記事で詳しく解説しています。

blog.cybozu.io

Coil + MetalLB + Calico

Coilは自社で開発した Kubernetes のネットワークプラグインです。必要最小限の機能を備えつつ拡張性に優れています。 Coil に Google 製の Layer 3 ロードバランサー実装である MetalLBと、ファイアウォール機能を提供する Calicoを組み合わせて高度なネットワーク機能を実現しています。

Contour + ExternalDNS + cert-manager

ContourEnvoyという高機能(Layer 7)ロードバランサを Kubernetes で制御できるようにするソフトウェアです。

Contour に ExternalDNScert-managerを組み合わせて、簡単な YAML を書くだけで HTTPS ウェブサービスを公開できるようにしています。

Elastic Cloud on Kubernetes (ECK) + TopoLVM

Elasticsearchという検索用ソフトウェアを Kubernetes で制御できるようにする Elastic Cloud on Kubernetes (ECK)を、自社製のストレージ管理ソフトウェアである TopoLVMと組み合わせて利用できるようにしています。

移行プロジェクト Maneki を開始

Neco のデータセンターが稼働開始したというのは、既存データセンターからの移行作業の始まりにすぎません。 そこで移行作業を集中して進めるべく、今月より新たに Maneki プロジェクトを開始することとしました。

f:id:cybozuinsideout:20190902154101j:plain
Maneki + Neco

Maneki + Neco で今後予定している最も大きな作業項目は以下の通りです。これ以外にも、大小の移行作業が見込まれています。

  • 既存検索エンジンのデータ移行
  • 既存データベースエンジンのデータ移行
  • Ceph / Rookを活用した分散ストレージの構築

採用イベントのご案内

以上ご紹介したとおり、Neco および移行プロジェクト Maneki では高度かつ大量の開発・移行作業を予定しています。 そこで、Neco もしくは Maneki プロジェクトの専任エンジニアを追加で募集いたします。

ご関心をお持ちの方は以下の募集要項から直接ご応募いただくか、ymmt に Twitter で DMをお願いいたします。

また、9月19日木曜日に Maneki + Neco のエンジニアと直接交流できる採用イベントを開催いたします。 以下のサイトからお申込みいただけます。

connpass.com

なにとぞよろしくお願いいたします。

2019年のエンジニア新人研修の講義資料を公開しました

$
0
0

こんにちは。皆様、夏はいかがお過ごしでしたか。
私は毎年実家に帰省し、そして毎年体調を崩すので、絶対風水的になんか合わないんだと思っています。コネクト支援チームのsakay_yです。

先日、2018年の新人研修資料を公開し、たくさんの反響をいただきました*1。ありがとうございました。
2019年もエンジニア新人研修を行いましたので、その紹介と講義資料を公開いたします。

2019年のエンジニア新人研修について

今年の研修は、組織運営チーム*2が取りまとめ、以下のような3構成となりました。

  • 必修講義
    • 誰に: 開発/運用本部に配属される新入社員​
    • 何を: どのチームに行っても必要となる基礎的な知識/技術/ツールを学び、体験できた
  • 選択講義
    • 誰に: 学びたい人が​(=新入社員に限らず)
    • 何を: 興味があることを学べた
  • チーム体験(2週間 * 3チーム)
    • 誰に: 開発/運用本部に配属される新入社員
    • 何を: 興味のあるチームで実際に業務を体験し、配属希望を決める参考になった

今年のようにチーム体験がメインの研修になったのは、開発本部の体制が変わったことも影響しています*3
既存の社員が主体的にキャリアを作れるように、カジュアルにチーム異動できるようになりました*4。ならば、新入社員も自分たちで選択できるのが自然です。
また、チーム側も体験入部がいつでもできる体制を整えていく必要がありすので、良い機会になります。

講義は、事前に講義ネタを社内のエンジニアに公募し、適切なメンバーに講師をお願いしました。 講義内容に応じて、必修講義と選択講義で分けました。

全体的に、新入社員の自主性を重視するように改善して、新人研修を実施いたしました。

スケジュール

  • 5/27〜5/31(第1週)
    • 必修の講義や演習が中心
    • 空き時間はRFC精読や予習/復習
  • 6/3〜7/12(第2〜7週)
    • チーム体験と選択講義期間
    • 2週間で体験するチームを変えて、合計3チーム

講義一覧

必修講義

1. 開運ことはじめ

2. HTTP/DNS

3. Linux

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

5. Git/GitHub

昨年とほぼ同じですので、今年の資料は非公開です。去年の資料は以下のとおりです。
2018-05 Git/Github

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

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

資料非公開。

8. ソフトウェアテスト&テスト自動化

選択講義

9. アクセシビリティ

10. 正規表現

資料非公開。

11. Chrome Developer Toolsの使い方

12. データベース

昨年とほぼ同じですので、今年の資料は非公開です。去年の資料は以下のとおりです。
2018-11 データベース

13. Docker

14. Webアプリケーションセキュリティ

15. ネットワーク

資料非公開。

16. ローカライゼーションと機械翻訳

資料非公開。

17. Linux サーバの CPU やメモリリソースの管理について

18. ベトナムの文化について

資料非公開。

19. 実践 Go 言語

20. Kubernetes を使った開発入門

Introduction to Kubernetes

21. CI/CD

22. パブリッククラウド入門 (AWSによる実習付き)

資料非公開。

23. スキーマファースト開発ハンズオン

GraphQL編

gRPC & Protocol buffers編

最後に

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

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

また、今回紹介した研修資料以外にも、各種勉強会やカンファレンスでサイボウズのエンジニアが発表した資料もエンジニアサイト(Cybozu Tech)で公開しています。 よろしければこちらもご覧ください!

Slides | Cybozu Tech

*1:2018年の新人研修についての記事はこちら https://blog.cybozu.io/entry/2019/02/22/163000

*2:開発本部の組織・評価・研修・労務などを取りまとめています

*3:開発本部の組織変更についての記事はこちら https://blog.cybozu.io/entry/2019/02/13/080000

*4:余談ですが、私も6月にさくっとチーム異動しました

「サイボウズ・ラボユース合宿2019」開催しました

$
0
0

こんにちは。サイボウズ・ラボの生田です。

湯河原のおんやど恵を会場に、8/26-29の日程でサイボウズ・ラボユース合宿を開催しました。前回から2年ぶりの開催です。今回はその模様をご紹介します。

サイボウズ・ラボユースと合宿の概要

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

そして、今回開催したサイボウズ・ラボユース合宿では、ラボユース生とラボメンバーがお互いに交流を深めることが目的です。また、メンターや合宿参加者と集中的に議論ができる場や、集中的に開発できる場をラボユース生に提供することも大きな目的の一つです。

今回の合宿の参加者は、現役のラボユース生4名(内、2名は研究生)、ラボユース卒業生5名、ラボメンバー9名の計18人でした。

1日目

初日は、13時に現地集合。自己紹介からスタートして、それぞれの興味や合宿で取り組む内容などを互いに共有しました。そこからは、晩御飯までもくもく、わいわい各自の取り組みを進めていました。

作業の様子(全景)

ラボメンバー西尾さん持参のRoboMaster S1も軽快に走る
ラボメンバー西尾さん持参のRoboMaster S1も軽快に走る

晩御飯後もしばらく作業を続け、その後は、温泉を満喫したりボードゲームを楽しんだりと、各々自由な時間を過ごしていました。

晩御飯の様子
晩御飯の様子
ボードゲームの様子
ボードゲームの様子

2日目

初日に続き、各自もくもく、わいわい作業を続けていました。ラボユース卒業生がラボユース生にアドバイスする場面や、ラボユース生同士がコミュニケーションをとる場面も多くありました。夕食後には、ラボメンバーの中谷さんによるVR体験会も開かれました。

ラボユース生とラボユース卒業生がディスカッションしている様子

3日目

最終日は、午前中は作業の続きや、合宿での成果発表の準備をしました。

そして、午後から成果発表が行われました。

まずは、現役ラボユース生に発表してもらいました。

西川さんは、機械学習を使った手話認識にチャレンジしています。この合宿では、HighwayNetとResNetをTensorFlowを使って実装する取り組みを行っていました。また、機械学習の勉強のために読んでいる、中谷さんが書いた本の感想も紹介していました。

西川さんの発表風景

平田さんは、C++を使った軽量の暗号ライブラリの開発に取り組んでいます。セキュリティに関する論文を読む取り組みと、実装した楕円曲線のアルゴリズムを高速化するためにメモリ処理を工夫する取り組みを行っていました。合宿期間中は、ラボユース生やラボユース卒業生と活発に交流しているのが印象的でした。

平田さんの発表風景

石巻さんは、完全準同型暗号の研究をしていて、ラボユースではより実装側に踏み込んだ取り組みをしています。今回の合宿では、その暗号を使った新しい取り組みに挑戦していました。今後どういう応用がされていくかが楽しみです。

石巻さんの発表風景

広瀬さんは、x64に対応した自作OSの開発に取り組んでいます。今回の合宿では、PIC(Programmable Interrupt Controller)を用いて、ポーリングで取得していた入力を割り込み式に改良する取り組みと、引数の処理を効率的に行うためコンソール側の処理を改良していました。メンターの光成さんやラボユース卒業生の内田さん、hikaliumさんと積極的に議論しながら作業を進めていたのが印象的でした。

広瀬さんの発表風景

次に、ラボユース卒業生に発表してもらいました。

赤間さんは、プラレールを使った論理回路を実現する取り組みの一環で、掛け算を実現する回路設計に取り組んでいました。レイアウトの省スペース化も同時に考慮していて、発表からもプラレールにかける熱意が伝わってきました。今回設計した成果物は、今年のオープンソースカンファレンスで展示予定とのことです。

赤間さんの発表風景

川田さんは、Rustを使ったデプロイツールIza(イザ)の設計と実装に取り組んでいました。現状のデプロイの方法に不満があって、もう自分で作っちゃえとなって取り組んだということです。個人的にそういうモチベーション大好きです。(発表資料はこちらです)

川田さんの発表風景

坂本さんは、所属しているロケット開発の学生団体で使う、ロケットのシミュレータの実装を行っていました。打ち上げ試験の様子などを動画を使って説明してくれて興味深かったです。

坂本さんの発表風景

緑川さんは、ラボメンバーですが、ラボユース卒業生でもあります。緑川さんは今回、専門であるセキュリティ技術のスキルアップのため、機械学習を使ったセキュリティアタックに関する論文を読み、発表ではその概要の紹介を行いました。

緑川さんの発表風景

内田さんは、自作OSを開発中で、今回はUSBマウス認識やマルチタスク処理の実装を行っていました。カウントアップタイミングの異なるタイマを表示するデモを行っていました。ちなみに、夜は日本酒の飲み比べやボードゲームを楽しまれていて、合宿を満喫されていました。

内田さんの発表風景

hikaliumさんは、自作OSのliumOSのUSBキーボード対応に取り組まれていました。合宿中に1000行近いコードの追加を行って、かなり進捗があったそうです。作業中も内田さんと活発に議論しながら開発に取り組んでいたのが印象的でした。

hikaliumさんの発表風景

さいごに

みなさん、それぞれのテーマを着実に進めていて、年度末開催予定の成果発表が非常に楽しみです。また、ラボメンバーだけではなく、ラボユース卒業生とも交流できるのは、合宿の一つの醍醐味だったんじゃないかと思います(私も良い刺激を受けました)。次回の開催も楽しみです。なお、サイボウズ・ラボユースは通年募集をしています。

集合写真

Yakumoのモニタリングとコンテナ時代のDataDog活用例

$
0
0

こんにちは、Yakumoチームの@ueokandeです。 秋といえばモニタリングですよね。 本日はYakumoプロジェクトにおけるモニタリングの取り組みについて紹介します。

YakumoはUS市場にKintoneを展開することをゴールとしたプロジェクトで、その一環として国内のデータセンターで提供しているKintoneをAmazon Web Service (AWS)に移行しています。 つい先日、Yakumoで開発・運用してるKintoneがリリースされました。 本記事では、Yakumoにおけるモニタリングと、YakumoチームでのDataDogの活用例について紹介します。

再入門・監視

監視とは、メトリクス、ログ、アラート、オンコール、障害対応などたくさんのことを扱います。 その中でもメトリクスのモニタリングは、サービスの健康状態を知るための大事な手がかりです。 Webサービスにおけるメトリクスは、障害対応やパフォーマンスチューニングなど様々なことに利用されます。

オライリー・ジャパン出版の『入門 監視』によると、“ビジネスを監視せよ” と書いてありますが、同時にシステムのメトリクスも役に立つとあります。 実際これまでのサイボウズの運用経験上、OSやミドルウェアなどのメトリクスはとても役に立っています。 たとえば一時的なサービス障害が発生したとしても、それを単なるシステム負荷として終わらせません。 障害発生当時のメトリクスやアクセスログを元に、なぜ発生したか原因究明までします。 そのための状況証拠として、アプリケーションやOSなどのメトリクスは欠かせません。

リリースしたばかりのYakumoは、パフォーマンスの改善よりも安定運用に力を入れてるフェーズです。 そのためYakumoでのモニタリングも、障害発生時に原因の切り分けや障害対応を迅速に行えることを第1目標としました。 この記事でも、OSやアプリケーションのメトリクスをモニタリングすることにフォーカスを絞って説明します。

Yakumoのモニタリングシステム

モニタリングシステムの方針

AWSにもCloudWatchというメトリクスを保存する仕組みはあります。 AWSのマネージドサービスは、標準でCloudWatchにメトリクスが蓄積します。 それとは別に、Yakumoが開発・運用しているサービスのモニタリングには、DataDogを利用します。

DataDogはOSやアプリケーションのメトリクスの収集と可視化の用途に利用しています。 逆にDataDogのアラート機能などはほとんど使用せず、ユーザー操作に影響する指標は別の仕組みで監視しています。

Kintone本体やKintoneの認証サービスなどはKubernetes (Amazon Elastic Kubernetes Service: EKS) にデプロイしています。 DataDogが収集する対象は、Kubernetes本体とNode、そしてKubernetes上にデプロイされているYakumoのサービスです。

SQSやKinesis Data Streamなどのマネージドサービスについては、引き続きCloudWatchメトリクスを利用しています。 これらのサービスもKintoneの運用を支えていますが、お客様への影響が少なく緊急度が低いと判断しました。 そのためKintoneのメトリクスと並べて比較しないということで、DataDogの対象にしませんでした。

なぜDataDogを選んだか

Yakumoでモニタリングシステムを導入するとき、いつくかのサービスを選定しました。 その中でDataDogを採用した理由は主に以下のとおりです。

  • CloudWatchと比較して操作性が高い
  • Kubernetes/Dockerとの親和性が高い
  • 社内で運用実績や知見が溜まってる

特に3つ目の理由が大きかったです。 国内データセンターで提供してるKintoneのモニタリングにもDataDogを採用しています。 そのためDataDogの運用の知見やJavaアプリケーションのモニタリングの知見が社内に十分溜まっていたため、YakumoでもDataDogを採用しました。

DataDogのデプロイとモニタリングの設定

DataDogの構成

DataDogをKubernetes上で利用するには、以下の2つのアプリケーションをデプロイします (詳しいデプロイ方法やKubernetesマニフェストの記述例は公式ドキュメントにあります)。

  • DataDog Agent ... OSやアプリケーションからメトリクスを収集するプロセス。Kubernetes上ではDaemonSetリソースでデプロイできる。
  • DataDog Cluster Agent ... クラスタ単位で取得するメトリクス(Kubernetes APIサーバーやElasticsearchクラスタなど)を、どのDataDog Agentがモニタリングするかを選出するプロセス。Kubernetes上ではDeploymentリソースでデプロイできる。

上記の2つのアプリケーションをデプロイすると、特に設定が無くとも、OSとDockerのメトリクス、そしてKubernetes APIサーバーのメトリクスが収集されます。 次はアプリケーションのメトリクスのモニタリングです。

Autodiscovery

VMベースのアプリケーションをDatadog Agentでサービスをモニタリングしていたときは、モニタリングの対象を設定ファイルに記述していました。 一方Kubernetesなどのコンテナベースのアプリケーションでは、コンテナがどのノードに配置されるかわかりませんし、IPアドレスも動的に変わります。 これを解決するためのKubernetes上のプラクティスの1つとして、Side-carパターンがありますが、同じプロセスが何個もデプロイされて、効率的ではありません。 DataDogはv6からAutodiscoveryという仕組みが導入されました。

上に記述したとおり、Kubernetes上であってもDataDog Agentのデプロイは各Nodeに1プロセスでOKです。 Autodiscoveryを使うと、DataDog Agentはモニタリングの対象のPodをKubernetes APIサーバーに問い合わせます。 モニタリングの項目を増やすには、DataDog Agentではなく、デプロイするPodの定義に記述できます。 そのためDataDogの設定を複数人が管理せず、アプリケーション責任者がモニタリングの設定を管理できます。

例えば以下のYAMLは、NGINXのステータスAPI (ngx_http_stub_status_module) をモニタリングをする例です。 Podのannotationsフィールドに、モニタリングする項目、初期設定、モニタリングの対象の3つを記述します。 この設定は、DataDogのNGINX integrationを利用します。

# nginx-deployment.yamlapiVersion: apps/v1
kind: Deployment
spec:template:metadata:annotations:ad.datadoghq.com/nginx.check_names:'["nginx"]'ad.datadoghq.com/nginx.init_configs:'[{}]'ad.datadoghq.com/nginx.instances:'[{"nginx_status_url": "http://%%host%%:10081/nginx_status"}]'spec:containers:- name: nginx
          image: nginx

Spring Bootアプリケーションのモニタリング

YakumoではいくつかのサービスをKotlinとSpring Bootで記述しています。 Spring BootにはActuatorという、モニタリングやアプリケーションを管理する仕組みがあります。 Actuatorを導入すると、JVMのヒープ領域の使用量や、Jettyのメトリクスなどを取得できます。 これらをDataDogに公開することで、Spring Bootアプリケーションをモニタリングできます。

メトリクスの公開形式は自由に選択できますが、YakumoではOpenMetrics (Prometheus互換の形式) で公開しています。 Spring BootでOpenMetrics形式でメトリクスを公開するには、以下のパッケージを追加します。

// build.gradle
dependencies {
  implementation('org.springframework.boot:spring-boot-starter-actuator')
  implementation('io.micrometer:micrometer-registry-prometheus')
}

そしてアプリケーションプロパティに、公開するメトリクスやURLを設定すると、外部から観測できるようになります。

# application.properties
management.endpoints.web.base-path=/
management.server.port=19100
management.endpoints.web.exposure.include=prometheus

これをモニタリングの対象に含めるために、PodのアノテーションにOpenMetrics integrationの設定を追記します。

# my-app-deployment.yamlapiVersion: apps/v1
kind: Deployment
spec:template:metadata: # ... snip ...annotations:ad.datadoghq.com/my-app.check_names:'["openmetrics"]'ad.datadoghq.com/my-app.instances: |
          [{"prometheus_url":"http://%%host%%:19100/prometheus",
              "namespace":"openmetrics",
              "metrics":["jvm_*",
                "jetty_*",
                "http_server_requests_*"]}]ad.datadoghq.com/my-app.init_configs:'[{}]'

マネージドサービスのモニタリング

先にAmazonのマネージドサービスのモニタリングはCloudWatchを利用してると説明しました。 しかしElasticsearchやRDB (Amazon RDS) などのKintoneが直接利用するいくつかのサービスは、障害対応時に参照することが多いです。 そのためKintone本体のメトリクスと並べて可視化できるよう、DataDogのモニタリングの対象としています。

DataDogから外部のクラスタのメトリクスを取得するには、KubernetesのServiceリソースを利用します。 こちらもPodと同じようにannotationsにAutodiscoveryの設定を定義すると、DataDogはServiceリソースをモニタリングの対象に含めます。 以下はElasticsearchをモニタリングするためのServiceリソースのマニフェスト例です。

# kintone-elasticsearch-service.yamlapiVersion: v1
kind: Service
metadata:name: kintone-elasticsearch
  annotations:ad.datadoghq.com/service.check_names:'["elastic"]'ad.datadoghq.com/service.init_configs:'[{}]' # AWS Elasticsearch Serviceでは/_cluster/pending_tasks を提供しないので # "pending_task_stats": falseに設定するad.datadoghq.com/service.instances: |
      [{"url":"https://kintone-elasticsearch-service.us-west-2.es.amazonaws.com",
          "cluster_stats":true,
          "pshard_stats":true,
          "index_stats":true,
          "pending_task_stats":false}]spec:type: ExternalName
  externalName:"kintone-elasticsearch-service.us-west-2.es.amazonaws.com"

カスタムメトリクスの取得

Yakumoではプッシュ通知の配信にGaurunを利用しています。 Gaurunはアプリケーションの状態をHTTPエンドポイントとして公開してますが、DataDog標準の機能ではメトリクスを収集できません。

DataDogは標準の機能で取得できないメトリクスは、DataDogのカスタムAgentチェックを利用します。 カスタムAgentチェックは、Pythonスクリプトを記述して、独自のメトリクスを収集できます。

以下はGaurunをモニタリングする設定例です。 カスタムAgentチェックで利用するスクリプトはConfigMapで記述して、DataDog AgentのPodにファイルとしてマウントします。

# datadog-agent-daemonset.yamlapiVersion: apps/v1
kind: DaemonSet
metadata:name: datadog-agent
  namespace: kube-system
spec:template:spec:containers:- name: datadog-agent
          image: datadog/agent:6.13.0
          volumeMounts: # entrypointのスクリプトで/checks.dに置くと/etc/datadog-agentにコピーされる- name: datadog-checksd-config
              mountPath: /checks.d/
              readOnly:true # ... snip ...volumes:- name: datadog-checksd-config
          configMap:name: datadog-checksd-configmap
# datadog-agent-configmap.yamlkind: ConfigMap
metadata:name: datadog-checksd-configmap
  namespace: kube-system
data:gaurun.py: |
    from datadog_checks.checks import AgentCheck

    import json
    import urllib2

    __version__ = "1.0.0" # Gaurunが公開してるメトリクスをHTTPから取得するclass GaurunCheck(AgentCheck):def check(self, instance):
            host = instance['host']
            port = instance['port']
            tags = instance['tags']

            url = "http://%s:%s/stat/app" % (host, port)

            req = urllib2.Request(url)
            j = json.load(urllib2.urlopen(req))
            self.monotonic_count('gaurun.push_success.ios', j["ios"]["push_success"], tags)
            self.monotonic_count('gaurun.push_error.ios', j["ios"]["push_error"], tags)
            self.monotonic_count('gaurun.push_success.android', j["android"]["push_success"], tags)
            self.monotonic_count('gaurun.push_error.android', j["android"]["push_error"], tags)

まとめ

この記事では、Yakumoにおけるモニタリングと、Kubernetes上でのDataDogの設定例をいくつか紹介しました。 DataDogは社内で実績があるとはいえ、Kubernetes上のデプロイは初めての試みでした。

『入門 監視』にもあるように、モニタリングシステムは作れば終わりではなく、継続的に育てていくものです。 障害対応で必要なメトリクスに気付いたり、可観測性が低いサービスに気づくことができます。 Yakumoは9月にリリースされて、モニタリングシステムはまさに成長期なのです。 今後も継続的にモニタリングシステムを育てて、より迅速に障害対応やパフォーマンスチューニングができる仕組みを作っていく予定です。

Viewing all 689 articles
Browse latest View live