エンジニア社内留学制度を利用してAndroidアプリ開発を体験した話

こんにちは、事業開発部でデータ分析やデータエンジニアリングをやっている佐藤です。最近の楽しみはクックパッドマートで買ったコーヒー豆を挽いて淹れることです。

今日はクックパッド社内で実施されているエンジニア社内留学制度について紹介します。

エンジニア社内留学制度とは

エンジニア社内留学制度は「異動をすることなく短期的に他の部署でその部署の仕事をする制度」というもので2019年4月に作られました。 この制度は異動をせずに視野を広げたり自分のキャリアを考えるための制度であり、普段自分が関わらない技術や分野に対して新しいチャレンジをする機会を提供するための制度です。

エンジニア社内留学制度を利用することで、最大2ヶ月の間もとの部署の仕事から離れて留学先部署の業務に取りかかれます。これは全エンジニアが利用可能な制度です。
この制度の概要は上記のとおりですが、制度を利用して留学させる・受け入れる側を含めた関係者の狙いは下記のようなものとなります。

  • 留学生側
    • 他部署の業務に取り組むことで、視野を広げ、技術や分野において新しいチャレンジをする機会とする
  • 留学元部署
    • メンバーの目線を広げ、技術や分野の違うチャレンジをするなど成長の機会とする
    • 他部署の業務を詳細に知る社員を増やすことで、留学終了後もより円滑に協力できるようにする
  • 留学先部署
    • 短期的な開発リソースの確保
    • 自部署の業務を詳細に知る社員を増やすことで、留学終了後もより円滑に協力できるようにする

この制度が作られた後、サービス開発を行う部署から技術基盤の部署へのエンジニア留学が何件か実施されました。 自分はこの制度を利用して5月〜6月の2ヶ月間モバイル基盤部でAndroid留学を行いましたので、以降の内容ではそのAndroid留学に関して書いていきます。

Android留学の流れ

当記事の冒頭に書いたとおり、自分は普段は事業部でデータ分析やデータ整備作業などを主務として行っていました。 そんな自分が今回エンジニア社内留学制度を使ってAndroid開発に関する知識を身に着けようと思った動機はおおまかに下記の3つです。

  1. Androidエンジニアが足りないということで丁度モバイル基盤部がAndroid留学を募集してた(下記の図を参照)
  2. 部署でデータ分析をしているうちにモバイルの知識が必要になってきた
  3. Android留学を一回しておくと今後iOSで同じようなことをしたくなったときの取っ掛かりにもなりそう

f:id:ragi256:20200716142952p:plain
Android留学募集の様子

というわけで上長に相談し、次の目標を掲げての2ヶ月の社内留学が決定しました。

  • Android版クックパッドアプリのどの部分のコードでどうやってログデータを送ってるか把握する
  • Androidアプリのロギング処理をクライアント側で調査・デバッグできるようになる
  • 誰かが新たにロギング処理を仕込む際に、相談相手になったりコメントできるようになる
  • 今後もモバイル基盤部と協力してモバイルのログ周辺がより良くなるよう整備をしていけるようになる
  • モバイルエンジニアに依頼するばかりでなく自分でもログを仕込めるようになる

この時点でAndroidアプリ開発もKotlinもJavaも全く触れたこともありませんでした。完全に未経験の状態です。 このあたりの留学決定に関する流れは4月頭の1on1で相談したら即留学用チャンネルにinviteされ、3週間ほどの調整期間の後、留学を実施というスピード感でした。調整期間というのは元いた部署の仕事から離れても大丈夫なよう片付けるための期間だったので、特に何かしらの準備があったわけではありません。

やってみてどうだったか

留学期間で実際に着手したタスクは下記の4つでした。

  1. アプリ画面リファクタリングに伴うログ変更に関する調査と周知
  2. 古いコードのVIPER化
  3. モバイルアプリにあるロギング実装に関するドキュメント整備
  4. 旧ロギング実装のリファクタリング

各タスクについて個別に書いていきます。

1. アプリ画面リファクタリングに伴うログ変更に関する調査と周知

クックパッドが提供しているレシピアプリはiOS・Androidの両プラットフォームともにVIPERというレイヤードアーキテクチャを採用しています。このVIPERアーキテクチャ採用は2018年に決定したもので、今利用しているコードの中には旧アーキテクチャのままになっている箇所もあります。よって既存コードをVIPERのアーキテクチャに置き換える作業(通称VIPER化)が行われています。
最近行われたとある画面のVIPER化に伴って、意図せずログ送信内容が書き換わっている可能性が高いことがわかりました。そのため、その問題の調査と社内周知に留学初タスクとして取り掛かりました。実際にやったことはVIPER化の手順を追いかけ、ログ実装を読み、実際に送られたログデータの変化を確認するだけです。

2. 古いコードのVIPER化

初タスクでVIPER化の作業を追いかけて読んだため、Android開発の素振りとしてVIPER化に取り組むこととなりました。しかし、結論から言えばこのタスクは断念することとなりました。
理由は初めてのモバイルアプリ開発に対して、あまりに知識が足りなかったためです。開発するためのキャッチアップに時間を浪費してしまい、そのままでは定められた期間で留学の目的を達成することが困難と判断したためです。VIPERもそうですが、Rx・DI・マルチモジュール・Android知識など予め備えておくべき知識の諸々を学びながらの期間であったため、見てもらうためのPRの実装を作るまでに時間がかかってしまいました。初めてレシピアプリ開発に取り掛かる開発者も困らないようにと初学者用ドキュメントは整っており、それを読みながらの実装でしたがとにかく初めての概念が多いため覚えることがたくさんありました。
この点に関してはまずGoogle CodeLabsをやるのが良かっただろうというのが反省です。

3. モバイルアプリにあるロギング実装に関するドキュメント整備

VIPER化を断念した後、自分が何をするべきかを留学当初の目標に立ち返って考え、取り組むべき課題を考えることとしました。元々の目標の中心にあった「ログ周辺」の課題がなにかないか考えたところ、「レシピアプリ内で使われるロギングの実装がとっちらかっているように見えるのでなんとかしたい」という課題を留学期間中に感じていました。
そこで実際に取り組んだタスクがこのドキュメント整備と次の旧ロギング実装のリファクタリングです。
レシピアプリはiOS・Androidともに開発に参加しやすい状況を維持すべく、開発参加者への支援が手厚く用意されています。オンボーディングや開発者向けドキュメントなどがそうです。ですが、アプリから送られるログ周りに関しては専門家がいなかったため、包括的なドキュメントがありませんでした。 そこで留学という機会を利用して、レシピアプリ開発へ新規に参加するエンジニアでもロギング実装に困らないようなドキュメントを書きました。

4. 旧ロギング実装のリファクタリング

3番目のドキュメント整備タスクと並行して、古いログ送信処理を置きかえる作業を実施していました。旧ロギング実装はアプリ開発からしてみれば何か大きく問題点があるわけではなかったため、誰にも気づかれずそのままとなっていました。しかし、実際に送信されたログを保守・加工・分析を行っている側では微妙に扱いづらいものであり、ログデータを利用する側(分析者やデータ整備者)ではちょっとした負債となっていました。この分析サイドからみた負債を解消することが、旧ロギング実装リファクタリングの目的でした。 こういった負債の指摘やリファクタリング作業やドキュメント整備はログデータを送る側からも利用する側からも扱いやすい、より良いログデータ環境を目指そうという意識付けにも繋がりました。データ基盤はは送信箇所や分析箇所などの特定の箇所の改善では使いやすくなりません。実際の利用フローに合わせ、足並みを揃えてトータルの改善をすることで多くの人から喜ばれるデータ基盤となります。

上記4つのタスクをひたすらにこなしているうちに気づけば2ヶ月が経過してしまい、エンジニア社内留学が終了となりました。留学自体は終わりましたが、自分自身がクックパッド社内でデータに関わるいちエンジニアであるということには変わりがないため、今回得た経験を活かして今後もデータ分析環境の改善に取り組んでいくつもりです。

エンジニア社内留学からの副産物的成果

実際にやってみたところ、予想していなかった副産物的成果がいくつかありました。自分としては「完全なAndroid初心者では手取り足取り教えてもらうだけになりそう」と思っていたのですが、留学をしてみたら意外と好影響もあったようです。

1. Android入門者用のドキュメントが改善された

初めてのAndroid開発に参加するため、レシピアプリに関する全ドキュメントに目を通すこととなりました。この際に疑問に思ったところは片っ端から質問をするようにしていたため、ドキュメントの不備・陳腐化した内容・分かりにくい説明などはどんどん修正されていきました。

2. ログに関する議論が活発になった

留学先のモバイル基盤部はお昼会という名のデイリーミーティングと、週次で行われる振り返りミーティングがありました。リモート期間中だったので1これらのミーティングは全てZoom越しに行われました。このミーティングで同僚の着手タスクの概要や進捗状況を把握するわけですが、こういった日々の会話の中で常にログデータの取り扱いに関する話に対して質問やコメントをしたりし続けていました。
折しも社内でログの取り扱いに関する話題が活発化しているタイミングで、そういった議論に関して「今こういう話が活発ですよ」「このチャンネルでこういう議論がかわされていますよ」という誘導を会話の中でし続けていました。
ロギングのドキュメント整備で話し合う機会もあり、「他部署ではログデータをこう取り扱っている」といった部署横断的な知識の提供に繋がりました。

3. 今まで方針の定まっていなかったロギング実装に関して、話し合いの場を設けて合意をとった

「やったこと」の3つ目に書いてあるとおり、留学後にこなした業務の中で「ロギングのドキュメント整備」がありました。このドキュメント整備ですが、今まで明文化されていなかったものをドキュメントに書き起こすだけで済むかと思いきや、そうではありませんでした。
これまで言語化されていなかったため、明確になっていたなかった点がいくつもあったのです。ドキュメントを制定するに当たり、同時にプルリクエストレビューで多くの人と意識のすり合わせがなされました。また、PRだけでは決まりそうにない、ロギング実装に関する大きな意思決定のため有識者会議を開くこともありました。
多くの人が関わるクックパッドのレシピアプリ開発の方針決定に関わることになるとは留学前には考えていもいませんでした。

終わりに

クックパッドでのエンジニア社内留学制度の紹介と、その制度を利用したAndroid留学体験を紹介しました。
社内で異動することなく、別分野のエンジニア業務を体験してみるのは新鮮なことでしたし、自分が取り組める業務の幅も広がったと思います。また、初心者かつ異分野エンジニアが留学してみると、留学ならではの好影響も与えられるという発見がありました。

留学を終了した証書
無事に留学修了証書をもらいました


  1. クックパッドでは新型コロナウイルス感染症の拡大に伴い、2月から全従業員を対象に在宅勤務を実施しています。在宅勤務に対する取り組み例はこちら。記事1記事2

Trivy + AWSによるコンテナイメージ脆弱性検査パイプラインの構築

技術部セキュリティグループの水谷(@m_mizutani)です。最近はPCゲーム熱が再燃しており、今はCities: Skylinesに時間を溶かされ続けています。

クックパッドでは レシピサービス の継続的なサービス改善の他にも、生鮮食品販売プラットフォームの クックパッドマート やキッチンから探せる不動産情報サイト たのしいキッチン不動産 をはじめとする新しいサービス開発にも取り組んでいます。さらに内部的なシステムも多数あり、動かしているアプリケーションの数は300以上に及びます。これらのアプリケーションには多くのOSSパッケージが利用されており開発を加速させますが、同時にOSSパッケージのアップデート、とりわけ脆弱性の修正にも向き合う必要があります。

これまでクックパッドでは(重大な脆弱性が見つかった場合を除いて)各サービスを担当するエンジニアが事業や開発の状況にあわせてパッケージのアップデートなどをしていました。しかし、管理すべきアプリケーションが多くなってきていることから、全社で統一したパッケージの脆弱性対応の仕組みを整える必要がでてきました。その一環として各アプリケーションのデプロイで使われるコンテナに含まれるパッケージの脆弱性を把握するための仕組みを整えました。

この記事では社内でのパッケージ脆弱性の検査に対してどのような要求があり、それをどうやって実現したのかを紹介します。

脆弱性スキャンのパイプライン構築における要件

現在、クックパッドでは大部分のアプリケーションがコンテナ化され、Amazon ECS(Elastic Container Service)上で動作しています。また、そこへのデプロイも主にCodeBuildを使ったCI(Continuous Integration)の環境が整備されています。そのため、このCIの仕組を利用することで脆弱性スキャンの機能を構築することにしました。

構築にあたってはいくつか解決しないといけない課題や要件があったため、それをまず紹介します。

要件1) 観測からはじめる

CI/CDにおける脆弱性管理の文脈では「CIのパイプラインで脆弱性を検査し、脆弱性があった場合はCIを止める」といったものが多く語られているように思います。検出されている脆弱性をすべて無くしてからしかデプロイできないようにする、というのは確かに理想形ではありますが、実際の事業に照らし合わせてみると必ずしも正しいとは言えないと考えています。

例えば1つのパッケージのバージョンを上げることで破壊的な変更が入る、あるいは連鎖的に複数のパッケージも更新する必要があり、結果的に大幅な改修が必要になってしまう、ということはままあることと考えられます。これが事業的に一刻も早くデプロイしなければならない状態だとすると、現場判断で脆弱性スキャンの機能を無効にせざるをえない、ということがありえます。

もちろん、攻撃が成功しやすい・影響が大きいような脆弱性の場合は事業を止めてでも修正する必要があります。しかし、脆弱性の中には複数の条件を突破しないと攻撃が成立しないような種類のものも少なからずあります。そしてそれはアプリケーションの設定や実行環境に依存するため、一律に判断するのは困難です。CVSSなどによるスコアリングでも、結局は環境などに依存してリスクが変動してしまい、これをセキュリティチームから開発チームに押し付けることは互いにとってあまり良い結果にならないのではと考えています。

そのため、まずはコンテナ内のパッケージの脆弱性がどのくらいあって、どのように変動しているかを把握し、どうすればリスクの極小化ができるかの仮設をたてて検証していく必要があります。そのためにも全体像を把握できるようにまずは観測できる環境を整えるという要求事項を設定しました。

要件2) CIと密結合にしない

いくつかの脆弱性スキャンツールはCIの途中で実行することを想定して作られており、CIのスクリプトなどに埋め込んでシンプルに実行することができます。しかし、アプリケーション数が多くなってくるとそれに比例して脆弱性スキャンツールを動かすための管理・統制にかかるコストが大きくなってしまいます。これは脆弱性スキャンツールの導入だけでなく、例えばツールの仕様が変わるなどしてうまく動かなくなった際の障害対応とメンテナンスの手間も含まれてきます。

先述したとおり、クックパッド内では300を超えるアプリケーションが動いており、それら全てのCIでそういった管理をするのはあまり現実的ではありませんでした。そのため、既存のCIの仕組みとは完全に独立させ、CI側に影響を与えないような疎結合なシステムを構築する必要がありました。これによって、今後さらにアプリケーションの数が増えても容易にスケールできることが期待されます。

要件3) 脆弱性の発見だけでなく修正もとらえる

脆弱性スキャンツールを使う主な目的は脆弱性のあるパッケージの発見であるため、検査結果をそのまま閲覧・通知することでこれは達成できます。しかし継続的にコンテナをメンテナンスしていく場合、コンテナに含まれる脆弱性が修正された、という情報も役に立つことがあります。

  • 脆弱性のあるパッケージが含まれていたコンテナイメージ修正の進捗状況を把握できる
  • 脆弱性のあるパッケージを更新したつもりのコンテナイメージをビルドした際、意図したとおりにパッケージが修正できたのか把握できる
  • 脆弱性が発見されてから修正されるまでの期間を計測できる

これらを実現するためには各コンテナイメージの脆弱性の状態を管理する必要があります。

要件4) ベースイメージに含まれているパッケージの脆弱性を識別できるようにする

クックパッドではアプリケーション用のコンテナイメージを作成する際に利用できる、社内共通のベースイメージが用意されています。このイメージにはおおよそ共通して使われるであろうパッケージが事前にインストールされており、これを使うことでアプリケーション用イメージごとのビルドのステップを短縮しています。

しかし、ベースイメージからビルドされたコンテナイメージの脆弱性をスキャンすると、ベースイメージにもともと入っていたパッケージの脆弱性とアプリケーション用に新たにインストールしたパッケージの脆弱性が混在した結果が出力されてしまいます。発生ポイントがどこであれ修正するべき脆弱性は修正しなければなりません。ですが、ベースイメージを管理しているチームとアプリケーションを開発しているチームが異なるため、脆弱性の発生レイヤが混在して通知されてしまうと、どのチームが対応するべき脆弱性なのかが判断しにくくなってしまいます。このため、検出された脆弱性がどのイメージをビルドした際に入り込んでしまったのかを識別できるようにしたい、という要求が生まれました。

ベースイメージが1つだけであれば、そのイメージの検査結果との差分をみることで脆弱性の発生ポイントを判定できますが、ベースイメージが複数あるとその紐付けの情報を管理する必要がでてきます。Dcokerfileからビルドする場合は FROM を見ることでベースイメージのレポジトリはわかりますが、いつビルドされたイメージが実際に使われているのかまではわかりません。とはいえ手動で管理するのはあまりにも煩雑なので、自動的に判定するような仕組みが必要になります。

脆弱性スキャンツールの選定

脆弱性スキャンのツールとしてはTrivy を採用しました。選定にあたって他のOSSや製品の脆弱性スキャンツールとも比較をしたのですが、

  1. 単体のバイナリだけで簡単にスキャンが実行でき、小回りがきくこと
  2. 入力や出力もシンプルになっており自分たちのシステムとのインテグレーションが容易であること
  3. OSのパッケージおよびrubyなどランタイムのパッケージの脆弱性もまとめて把握できること

という3つの理由からTrivyを使うことにしました。

ちなみに、クックパッドではCI/CDにおけるコンテナイメージの保存にはAmazon ECR(Elastic Container Registry)を利用しており、ECRのImage Scanningの機能を利用することも検討しました。しかし、スキャンできる対象がOSのパッケージのみだったことから採用を見送りました。

ちょうど先日、AWS Security Blog で How to build a CI/CD pipeline for container vulnerability scanning with Trivy and AWS Security Hub というTrivyをCIに取り入れるというブログが公開されていました。このブログでもCodeBuildでのCIを想定しており、CIの中にTrivyによる脆弱性スキャンを実行して、その結果をSecurity Hubに格納するというアーキテクチャについて述べられています。このアプローチも小さくはじめるにはよい構成なのですが、先述した要件をクリアするのは十分ではなかったため、我々は別のアーキテクチャによって脆弱性スキャンのパイプラインを実現しました。

アーキテクチャと実装

TrivyとAWSの各種マネージメントサービスを利用し、コンテナイメージの脆弱性スキャンパイプラインを構築しました。AWSのサービスと接続することから、基本的な制御の部分にはLambdaを利用し、サーバレスなアーキテクチャになっています。デプロイにはAWS CDK(Cloud Development Kit)を利用しています。

また、アーキテクチャ図からは省いていますが、スキャン結果から得られたデータを確認するためのWeb管理コンソールも用意しています。

イメージのスキャン

f:id:mztnex:20200713200112p:plain
イメージスキャンに関連するAWS構成

クックパッドでは原則コンテナイメージをCodeBuildでビルドし、ECR(Elastic Container Registry)にプッシュしたのち、ECS(Elastic Container Service)へデプロイするという構成になっています。要件2の疎結合なアーキテクチャにするという観点から、今回はCodeBuild内で実行されるビルドのプロセスには一切手を加えず、ECRにプッシュされたイメージを利用することで、CI/CDのパイプラインに一切影響しないような構成にしました。

スキャンの開始は2つのトリガーがあります。1つはイメージがプッシュされた際にCloudWatch Events経由で送信されるECRイベント、もう1つは定期的(現在は24時間ごと)に発行されるCloudWatch EventsのScheduledイベントです。それぞれのトリガーによって起動されたLambdaがスキャンすべき対象のイメージの情報をキューとしてScanQueueに詰めます。定期的に実行されるトリガーはECRからレポジトリの一覧を取得し、そこからスキャンが必要なイメージを選定します。

ECRにプッシュされたイメージの中身は後からは変更されないため、同じ脆弱性を見つけるためには何度もスキャンする必要はありません。しかし脆弱性スキャンツールにTrivyを使う場合、新たに発見された脆弱性を見つけるためには脆弱性DBを更新して、再度検査をするというのがシンプルな対応になります。そのため、イメージがプッシュされたイベントとは別に定期実行の仕組みを取り入れました。

Trivyを使った実際のスキャンはFargate上で実行することにしました。Fargateを選択した主な理由は、1) 実行環境が独立しているため、ECSのように他のタスクに影響を及ぼさない、2) スケールアウトが容易、の2つになります。特に定期スキャンでは数百のイメージをスキャンするためのキューが一度に発生するため、スケールアウトによって短時間でスキャンを完了させられます。Fargate上ではこのパイプラインを制御するためのプログラムを動かしており、それがTrivyを起動させます。具体的には、次のような制御をしています。

  1. ScanQueueからスキャン対象イメージの情報を取得
  2. 脆弱性DBの更新(図中では割愛)
  3. Trivyの起動とスキャン結果の保存
  4. 対象イメージのレイヤ情報をECRから取得
  5. スキャン結果をS3に保存
  6. スキャン完了通知をResultQueueに送る

Trivyのスキャン結果は多少のメタデータを付与したあと、なるべくそのままS3に保存します。これのデータをもとに結果処理のLambdaが管理コンソールからの検索に必要なインデックス情報などをDynamoDBに保存します。

脆弱性の状態管理

f:id:mztnex:20200713200216p:plain
脆弱性の状態を管理するためのAWS構成

脆弱性の状態を管理するのに必要なのは「直前のスキャン結果との比較」です。これはRDBを使って管理するというようなアプローチもありますが、今回はS3に保存してあるスキャン結果を単純に比較してコンテナイメージに含まれる差分を計算する、という方法にしました。これによってイメージごとの差分計算処理が1つのLambdaに集約され、大量のリクエストがきても容易にスケールアウトできます。

差分計算の処理はシンプルに最新のスキャン結果と直前のスキャン結果を比較しているだけです。最新のスキャン結果が保存されたS3パスが(「イメージのスキャン」のアーキテクチャ図にもあった)スキャン結果処理のLambdaから送信されたQueueに、直前のスキャン結果が保存されたS3パスがDynamoDBにあります。これらをもとに、それぞれのスキャン結果をS3からダウンロードし、新しく出現した脆弱性と削除された脆弱性の情報を比較結果としています。比較結果のデータサイズがSQSのデータサイズ制限(256KB)を超える可能性があるので、比較結果を直接SQSには流さずS3へ保存しています。その後、SNS → SQS を経由して Lambda に通知を送り、DynamoDB上にある脆弱性の状態(未修正・修正済み)を更新したり、Slackに通知したりしています。

f:id:mztnex:20200713200248p:plain
新たな脆弱性が発見された、あるいは脆弱性が修正された際のSlack通知

管理コンソールからはどのコンテナイメージのどこにその脆弱性があり、それぞれの修正状況も把握できるようなユーザインターフェイスを用意しました。これによって社内での脆弱性対応の進捗が可視化されています。

f:id:mztnex:20200714103346p:plain
脆弱性の修正状況を確認できる管理コンソールのビュー

ベースイメージの判定

f:id:mztnex:20200713200445p:plain
ベースイメージを判定する手順の概要

「要件4) ベースイメージに含まれているパッケージの脆弱性を識別できるようにする」で説明したとおり、ベースイメージに含まれているパッケージの脆弱性とアプリケーション開発によって追加されたパッケージの脆弱性とを区別する仕組みを取り入れました。この判定には各イメージのLayer Digestを利用しています。ベースイメージを利用してイメージをビルドする場合、ビルドしたイメージは一部のレイヤーをベースイメージと共有しています。そのため、Layer Digestが一致すればそれ以前のレイヤーは基本的にすべてベースイメージのものである、と判断することが出来ます。

Trivyのスキャン結果には各脆弱性が含まれるレイヤーのLayer Digestが記載されているため、アプリケーションイメージのどのレイヤーがベースイメージ由来なのかがわかっていれば、脆弱性を含むパッケージがどちらに属しているのかも判断できます。どのレイヤーからベースイメージなのかを後から判定するため、スキャン結果とLayer Digestの一覧を組み合わせて保存しておく必要がありますが、残念ながらTrivyのスキャン結果に記載されていません。しかしLayer Digestの一覧はECRに保存されているため、代わりにECRへアクセスすることで取得できます。先述したとおり、fargate上でのスキャン時にはTrivyのスキャン結果とECR上のレイヤ情報の両方を取得し、組み合わせてS3へ保存しています。

このような仕組みでベースイメージを検出するために、検索用のデータストアとしてDynamoDBを使っています。DynamoDBに全てのイメージの最新レイヤーのLayer Digestをキーとして保存し、アプリケーションイメージの脆弱性一覧を表示するタイミングで全てのLayer Digestをバッチで問い合わせ、その結果からどこからベースイメージかを判定します。一覧表示のタイミングで検索しているのは、ベースイメージとアプリケーションイメージがほぼ同時に更新された際、スキャン結果の到着が前後する可能性があるためです。

この仕組を使うことで、どのレポジトリやタグがベースイメージとして使われているのかという情報をメンテナンスしなくても、自動的に判定ができるようになりました。また、ベースイメージが複数ある(ベースイメージAからベースイメージBが作られ、ベースイメージBからアプリケーションイメージが作られる)場合でも、同じ仕組みによって正確に複数のベースイメージを判定できます。管理コンソールでは次の図のようにベースイメージ由来の脆弱性はリンク先で確認するようなUIにしました。

f:id:mztnex:20200714103605p:plain
ベースイメージとアプリケーションイメージの脆弱性情報が分かれて表示される

コスト

今回のアーキテクチャではコスト削減を目的としていたわけではないのですが、結果としては一日あたりの動作コストが$6弱になりました。

その中でも支配的なのがDynamoDBで、1日あたり$4ほどのコストになっています。これはCapacity設定の最適値が読めないため on-demand capacity mode で動作させているためと考えられ、これは今後適切な値でRead/Write Capacityを設定しAuto scalingと併せて使うことで改善できると考えています。また、クエリについても改善の余地がありそうな部分はあり、そちらも今後リファクタしていきたいと考えています。

一方、CPUリソースが必要とされるTrivyのスキャンに関しては一日あたりおよそ$0.5ほどになっています。これはスケールイン・アウトがうまく機能していること、そしてFargate spotを使っていることで大きくコストを抑えていると見ています。Fargate spotなので処理の途中で停止してしまう可能性もありますが、どの段階で処理が止まってもやり直しがきき、かつ複数回処理が実行されても冪等になるように実装しているため、特に問題なく利用できています。

まとめ

この記事ではTrivyとAWSのマネージドサービスを使った、CI/CDと疎結合にコンテナイメージの脆弱性スキャンパイプラインの要件、アーキテクチャと実装の一部を紹介しました。これは永続的に疎結合のまま運用することを目指しているわけではなく、CI/CDの中に直接組み込むとしたらどのような仕組みや運用ポリシーが必要になるか?という課題を解くための前段階という意味合いもあります。技術部セキュリティグループでは引き続きどのようなパッケージの脆弱性管理の戦略をとれば事業開発のスピードへの影響を最小化しつつセキュリティを担保していけるか、という問題にチャレンジしていこうと考えています。

このようなエンジニアリングのチャレンジをするにあたり、クックパッドでは(引き続き)セキュリティエンジニアを募集しています。情報セキュリティに強い方だけでなく、むしろサービス開発を得意としつつセキュリティにも強い関心がある、という方にも興味を持っていただければ幸いです。

クックパッドのサービスメッシュ基盤を改善した話

こんにちは、技術部 SRE グループの ryojiro (@flyhigh_ro) です。今回はクックパッドでのサービスメッシュ基盤を改善した話を紹介します。クックパッドでのサービスメッシュの構成については以前の記事をご覧ください。

クックパッドでは多くのサービス間通信において Envoy を利用していますが、以下のような問題を抱えていました。

  • 改善前の Envoy のバージョンは v1.9.0 (2018/12 リリース) と古く、開発者はそれ以降に実装された機能を利用することが出来なかった。
  • CDS/RDS を cookpad/itacho によって生成しているため、 v1.9.0 で利用出来る機能であっても cookpad/itacho で実装されていなければその機能を利用できなかった。利用するためには cookpad/itacho にその設定を実装する必要があり、面倒だった。
  • cookpad/itacho で既に実装されている機能でも、ドキュメンテーションが不十分で目的の Envoy での設定に対応する itacho の設定が調べられず、cookpad/itacho の実装を調べることがあった。

上記の理由から、サービス開発者が Envoy v1.9.0 以降の機能や cookpad/itacho で実装されていない機能を利用したくても、すぐにその機能を利用することができずに、その機能を利用することを諦めることが何度かありました。SRE としては、サービス開発者にサービスメッシュを積極的に活用してもらいたいと考えていたので、サービスメッシュをもっと手軽に利用してもらうことを目的として、以下の内容でサービスメッシュ基盤を改善しました。

  • Envoy のアップデート
  • v1 xDS API の廃止
  • cookpad/itacho での itacho generate 廃止
  • xDS API の CI 整備

Envoy のアップデート

クックパッドで利用されている Envoy のバージョンは v1.9.0 と 2018/12 にリリースされた古いバージョンを利用していました。v1.9.0 でも機能としては十分でしたが、脆弱性が報告されていたり、古いバージョンを使い続けることでアップデートがどんどん大変になっていくことに懸念がありました。そのため、今回を機に最新のバージョンまで上げることにし、以降もバージョンアップしやすい環境を目指すことにしました。

段階的な移行

最初は Envoy を一気に v1.9.0 から v1.14.2 まで上げようと考えていましたが、以下の理由から一度 v1.12.0 にしてから v1.14.2 に上げることにしました。

v1.14.2 だと既に deprecated になっている設定があり、v1.9.0 と v1.14.2 で互換性のない設定があった

envoy.api.v2.route.HeaderMatcher.regex_match を例にすると、 v.1.14.2 では既に deprecated となっているため envoy.api.v2.route.HeaderMatcher.safe_regex_match へ移行する必要がありました。しかし envoy.api.v2.route.HeaderMatcher.safe_regex_match は v1.9.0 では実装されていません。一旦全ての Envoy を envoy.api.v2.route.HeaderMatcher.regex_match と envoy.api.v2.route.HeaderMatcher.safe_regex_match に対応しているバージョンへアップデートし、envoy.api.v2.route.HeaderMatcher.regex_match を envoy.api.v2.route.HeaderMatcher.safe_regex_match へと移行してから v1.14.2 にアップデートする必要がありました。

cookpad/itacho で利用しているライブラリの protobuf 定義が古く、v1.12.0 までの xDS リクエストにしか対応していなかった

cookpad/itacho で利用しているライブラリの protobuf 定義が古く、v1.13.0 以降の Envoy から送信される xDS request のデシリアライズに失敗していました。cookpad/itacho に原因があることはわかっていましたが、cookpad/itacho を開発した経験がなく、この対応にどの程度工数がかかるのか見積もることができませんでした。そこで、一旦 v1.12.0 へアップデートすることにして、その間に cookpad/itacho へ対応することにしました。

v1 xDS API の廃止

Envoy v1.10.0 で Bootstrap config の deprecated_v1 sds_config と command line config の –v2-config-only オプションが廃止、 v1.13.0 で v1 xDS API が廃止となりました。クックパッドではいくつかのアプリケーションで v1 xDS API を利用してたので、それらを全て v2 xDS API へと移行しました。Envoy 以外から v1 xDS API を利用しているアプリケーションもあったので、それらも v2 xDS API を利用するように変更しました。

cookpad/itacho での itacho generate 廃止

クックパッドでは CDS/RDS のレスポンスの生成に itacho generate を使用していました。itacho generate は指定された設定に沿って CDS/RDS を生成します。しかし、Envoy の設定名とそれを生成する itacho generate の設定名が一致していなかったり、ドキュメントが整備されていないことから、どのような記述をすればいいのかわからないとの声が上がっていました。実際に itacho generate の設定を確認するために直接実装を確認することもありました。また、新規の機能を利用する場合も cookpad/itacho へその機能を実装する必要があり、手軽に新規の機能を利用することが困難でした。これらの課題を解決するために、itacho generate で xDS API レスポンスを生成することをやめ、直接 xDS API レスポンスを記述するように変更しました。そのまま全てのレスポンスを記述すると冗長になってしまうので Jsonnet で記述するようにしました。共通の設定は関数化し、upstreams 毎に設定を libsonnet ファイルにまとめて、それらを import して利用することで簡潔に記述できるように工夫しています。以下は itacho generate での記述例とxDS API レスポンスをそのまま記述したときの例です。

itacho generate での記述例

https://gist.github.com/ryojiro/baac94ceb615949c7ea54e36ba94b70a

xDS API をそのまま記述した例

https://gist.github.com/ryojiro/cde4f0024cd29b6ed4ee10467519f1fb

このような記述にすることで、upstreams の設定を1箇所で管理しつつ、サービス毎に独自に upstream の設定を上書きすることも可能となっています。また、新しい設定を記述する時にも Jsonnet へ設定を追加するだけなので、手軽に Envoy の機能を利用できるようになりました。

xDS API の CI 整備

これまでは xDS API レスポンスを itacho generate によって生成していたので、正しい xDS API の形式となっていることが保証されていました。しかし Jsonnet で xDS API レスポンスを生成するように変更したことで、生成される xDS API レスポンスが正しいことが保証されなくなってしまいました。そこで、CI を整備して生成される xDS API レスポンスが正しい形式となっていることを事前に検証するようにしました。Envoy のドキュメントを読むと mode オプションvalidate を渡して起動することで、Envoy の設定が正しいかを検証できそうでしたが、ネットワーク通信が発生しないので xDS API サーバーを立てて生成した xDS API レスポンスを検証することはできませんでした (静的な設定ファイルのみ検証されます) 。検証したいのは CDS/RDS のレスポンスで、Envoy の static_resources との設定はほとんど同じだったので、CI では設定した xDS API レスポンスを静的な設定ファイルに変換し、その設定ファイルで Envoy を起動することで、設定した xDS API レスポンスが正しい形式で記述されているかを検証するようにしました。クックパッドでは現在 v1.12.0 と v1.14.2 の Envoy が混在しているので、どちらも valid な設定のみ追加できるように、それぞれのバージョンで検証するようにしています。

最後に

今回はサービスメッシュをサービス開発者により手軽に利用してもらうために、サービスメッシュ基盤を改善した話を紹介させていただきました。この改善によって、実際にサービス開発者が新しい Envoy の設定を追加して利用する事例も生まれています。Envoy は比較的新しいアプリケーションでまだ知見も少ないと思うので、これからサービスメッシュ基盤の改善を考えている方の参考になれば嬉しいです。

このエントリを読んで興味を持った方や、数千の規模で Envoy が利用されているサービスメッシュ基盤を改善したい方はぜひ以下のサイトよりご応募ください。

クックパッド採用サイト: https://cookpad.jobs