OSS | AI tech studio https://cyberagent.ai AI事業本部は、サイバーエージェントグループのデジタルマーケティング分野におけるサービス開発を行う組織です。 Fri, 27 May 2022 02:00:26 +0000 ja hourly 1 https://wordpress.org/?v=5.3.26 EBPMデータベースを作った理由 https://cyberagent.ai/blog/tech/oss/16582/ Fri, 27 May 2022 02:00:47 +0000 https://ai-blog-admin.cyberagent.ai/?post_type=blog&p=16582 経済学社会実装チームの森脇です。

本日、証拠に基づく政策(Evidence-Based Policy Making, EBPM)を促進するためのプラットフォーム、EBPMデータベースをリリースしました。このウェブサイトは査読付ジャーナルから政策現場における簡単なデータ分析までさまざまなレベルの政策に関するエビデンスを集めて共有することを意図したプラットフォームです。

大学やシンクタンクの専門家、行政組織の実務家などに政策効果に関する簡単なサマリー記事を「レビュー」という形で登録してもらい「データベース」化することで、政策を検討する際に過去の類似例の検証結果について参照できるようにしています。

EBPMデータベースのコンテンツは専門家のみなさまにどんどん追加していただきたいのですが、取り急ぎイメージを共有するために教育、医療、少子化の3分野について10本程度のレビューを掲載しています。いずれも分野の専門家が選定した査読付ジャーナルからの質の高いエビデンスを集めています。協力いただきました、伊藤寛武さん、高橋雅生さん、茂木良平さんにはこの場をお借りして御礼申し上げます。

EBPMは政府における議論がはじまった2017年ごろからバズり始め昨年急速に認知されるようになりました。厚生労働省では因果推論手法を用いた政策効果分析が発表されるなど着実に取り組みが進んでいます。私自身も中央省庁から政策のデータ分析に関するヒアリングを受ける機会があり政府におけるニーズの高まりを感じているところです。

trend

EBPMの検索数(Googleトレンド)

人的・財政的に慢性的なリソース不足に陥っている行政組織にとって、限られたリソースを有効な政策に振り向けることは喫緊の課題です。国の経済政策の司令塔である経済財政諮問会議においてEBPMアドバイザリーボードがエビデンスづくりを進めていますし、今年4月、経済産業研究所にEBPMセンターが設立されています。こうした、政府の取り組みはGoogleをはじめとするテック企業がA/Bテスト(ランダム化比較実験)によって客観的なエビデンスに基づいてサービスの改善を繰り返して成長してきたことと付合し、時代の趨勢と言えます。

EBPMデータベースの提供する価値

EBPMデータベースでは、主に3点の価値を提供したいと思っています。

低コストのEBPM

中央省庁のEBPMの取り組みは、「データを分析して」「証拠を生み出す」ところにフォーカスしているように見受けられます。日本最大のシンクタンクといわれ独自の研究所や傘下の研究独法をもつ中央省庁はともかく、自治体の現場でEBPMのためのデータ分析を行うのは容易ではありません。データ収集、分析、結果の活用という三段階のなかで、データエンジニア、データアナリスト、そして何より解析結果を幹部や議員に説明して意思決定を促す政策コンサルタントと、複数種類の専門家が揃うことはなかなかないと思います。

EBPMデータベースでは、すでに専門家がデータ収集と分析を行なった結果がレビューというかたちで投稿されています。適当なレビューを探して最後の意思決定の部分から始めることができます。必要なエビデンスが十分整理されていないといったご要望はお気軽にGitHubもしくはebpm@cyberagent.co.jpまでご要望ください。

EBPMについてのオープンな議論

EBPMデータベースのような試みは、諸外国でWhat Worksとかエビデンスストアといった名称で多数公開されています (注1)。しかし、これらは一方通行の情報提供にとどまり、研究者や政策現場とのインタラクションが難しくなっています。

EBPMデータベースでは、GitHubにソースコードをすべて公開しており、自由にレビューの追加や修正要望をできるようになっています。研究者の方がご自身の研究をアピールする場として使っていただくもよし、なんちゃって専門家によって歪んだ世論を糺すために分野で認められた高品質のエビデンスを追加していただくもよし、政策現場の方が実施してみたデータ分析結果を投稿していただくもよし、自由に使っていただければ幸いです。また、投稿されたレビューについての改善も歓迎しています。

政策現場とアカデミアの結節

COVID-19を契機に政策決定過程における専門家の役割が改めて議論されています。頑健なエビデンスをじっくり作り上げることに長けた専門家が、意思決定のためにオンデマンドなエビデンスを短時間で生み出す必要がある政策現場で何ができるのか。

急ぐあまりエビデンスの質が落ちれば仲間内から批判され、エビデンスの生産が遅いと政策現場からは見放されるという板挟み状況が改めて浮き彫りになりました。逆に、政策決定側は専門家に過剰に責任を負わせる構図も批判の対象となりました。

このような状況を打開するためには、EBPMデータベースを通じてエビデンスをつくる人と使う人が交流することでお互いの言語やインセンティブ、制約について相互理解を深めることで協働体制をつくっていくことが有用ではないかと思います。

既存のエビデンスについて共有するだけでなく、行政の現場での実証実験に専門家が参加し、その結果得たデータを論文化してまたEBPMデータベースに投稿するといったサイクルが生まれることを期待しています。

最後に

EBPMデータベースは開発途上のツールです。たくさんのレビューを投稿していただくとともに、実際に政策現場のユーザーに使った感想をフィードバックをいただき発展させていければと思います。

 

注1: What Works Clearing House, What Works Center for Local Growth  Education Endowment Foundation Teaching and Learning Toolkit  Kopernik Solution Catalog  J-Pal Crime Reduction Toolkit What Works For Children’s Social Care 3ie Development Evidence Portalなど

]]>
Cluster of Kubernetes Clusters ~connecting palm-sized personal Kubernetes clusters with 100 Raspberry Pi~ https://cyberagent.ai/blog/pr/kubernetes/4340/ Sat, 19 Jan 2019 06:00:50 +0000 https://cyberagent.ai/techblog/?p=4340 Hello.

I’m @amsy810, and I work at CyberAgent, Inc.

 

The era of palm-sized personal Kubernetes clusters is here!

Everyone over the age of a middle school student has a personal Kubernetes cluster, right?

We held our 3 Day Cooking -Raspberry Pi with Kubernetes- event again.

This time, we created a palm-sized personal Kubernetes cluster with a hundred Raspberry Pi.

 

Preparation

Like last time, we purchased a hundred Raspberry Pi and other various components in advance.

 

Checking all of them was hard work.

 

 

Learning

This time, we created 30 physical personal kubernetes clusters with 100 RaspberryPi.

 

 

After that, starting with etcd configuration, and further setting up kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy, flannel, the interns were able to understand Kubernetes architecture on a deep level in these 3 days.

 

Challenges

On the final day, we aggregated the personal Kubernetes clusters.

 

Captain Kube’s chivalry is something to behold.

And he is so cute!

He was a real lifesaver to Phippy!

The cluster looks like this from the top

And like this from the side.

This time, we gathered interested interns and learned about Kubernetes.
We are excited to work with them in the future!

]]>
DruidをBigQueryの代わりに使えないか試してみた https://cyberagent.ai/blog/tech/oss/4095/ Mon, 26 Feb 2018 05:05:31 +0000 https://cyberagent.ai/techblog/?p=4095 こんにちは。アドテクスタジオの和田です。

現在私の所属しているチームではBigQueryでアクセスログ分析を行っているのですが、コスト問題、アドホックなクエリを実行したい、大量join時の待ち時間など運用上の課題を幾つか抱えていました。

そこで代替手段を探していたのですが、Druidというデータストアがあることを知ったので課題をDruidで解決できないか検証してみました。

 

Druidとは

OLAP向けのデータストアです。データソースとしてS3, GCSのようなFileベースのデータに加えてkafkaなどのstreamingデータも扱うことができます。

http://druid.io

 

Druidのアーキテクチャ

Druidは以下のコンポーネントで構成されます。

  • Coordinator Node
    • セグメント管理ノード
    • セグメントはHistoricalノード上で管理されているが、古いセグメントの削除や負荷分散などの管理を行う
  • Historical Node
    • セグメントを実際に持ち、ロードなどを行う
  • Indexing Service
    • セグメント生成ノード。以下コンポーネントをまとめて Indexing Service と呼ぶ
      • Overlord:データ取り込みのリクエストを受け付けるエンドポイント
      • MiddleManager:Peonを管理する
      • Peon:taskを実行する。taskとはデータロードしてセグメント生成する一連の処理
  • Broker Node
    • queryを受け付け、読み込み対象となるセグメントを持つノードへルーティングを行う
  • Realtime Node
    • リアルタイムインデックスを提供する
    • セグメントを生成してHistoricalノードに保存する
  • Zookeeper
    • セグメント情報などの管理用

※引用:  http://druid.io/docs/0.11.0/design/indexing-service.html より

 

検証構成

以下のインスタンスを用意し、quickstartを参考に各コンポーネントを起動します。

  • Node1 2vCPU / 8GBmem
    • Coodinator
    • Overlord
    • Zookeeper
  • Node2 8vCPU / 48GBmem
    • MiddleManager
    • Historical
  • Node3 2vCPU / 8GBmem
    • Broker
  • 可視化用
    • superset Node

Coodinatorプロセスが8090ポートをLISTENしているのでブラウザからアクセスするとタスク状況が確認できます。

http://Node1:8090/console.html

 

データロード

データロードは上記にもあったtaskという単位で管理します。

定義ファイルをjsonで記述してOverlordプロセスへHTTP POSTすることでtaskを登録できます。

taskのtypeは以下のようになります。

  • index
    • 小さなデータセット向けの取り込みタスク。大きいものはindex_hadoopを使う
    • firehoseというデータロード向けのコンポーネントを利用する
      • ローカルファイルやhttp、既存セグメントのcombineタスクなどが定義できる
      • 外部extension(jar)を使うことでS3 / GCSデータロードを実現する
  • Hadoop
    • 大きいデータセット向けの取り込みタスク
    • 取り込み元ファイルの拡張が難しい?(firehoseのようなextensionはありませんでした)
    • ローカルファイル / S3はロードすることができる。
    • http://druid.io/docs/latest/ingestion/batch-ingestion.html
  • 以下のようなタスクもある
    • append / merge / same_interval_merge / kill / noop

 

データロード検証

index (GCS firehose)

druid-google-extensionsをインストールし、GCS firehoseでGCSからデータロードするindexタスクを定義します。

以下のjson定義をOverloadプロセスにPOSTで登録することでgzip圧縮済みのアクセスログを1日分ロードしてみます。

{
  "type": "index",
  "spec": {
    "ioConfig" : {
      "type" : "index",
      "firehose" : {
        "type" : "static-google-blobstore",
	"blobs": [
          {"bucket": "test-druid-store", "path": "test/deployment.2018-01-29_00.log.gz"},
          {"bucket": "test-druid-store", "path": "test/deployment.2018-01-29_01.log.gz"},
          ...ロードファイル数分記述...
	]
      }
    },
    "dataSchema" : {
      "dataSource" : "test",
      "granularitySpec" : {
        "type" : "uniform",
        "segmentGranularity" : "day",
        "queryGranularity" : "none"
      },
      "parser" : {
        "type" : "string",
        "parseSpec" : {
          "format" : "json",
          "dimensionsSpec" : {
            "dimensions" : [
              "id",
              "time",
              "reqid",
              "uid",
              "os_version",
              "processing_time"
            ]
          },
          "timestampSpec" : {
            "format" : "auto",
            "column" : "time"
          }
        }
      },
      "metricsSpec" : [
        {
          "name" : "count",
          "type" : "count"
        }
      ]
    },
    "tuningConfig" : {
      "type" : "index",
      "maxRowsInMemory": 750,
      "targetPartitionSize": 100000
    }
  }
}

 

task完了まで7時間程度かかりました。。データサイズは 750MB程度でした。

root@Node2:~/druid-0.11.0# du -sh var/druid/segments/test/
755M var/druid/segments/test/

 

index_hadoop

次に index_hadoop type でのデータロードで高速化できないか試してみます。

firehoseは利用できなかったので、node2サーバにgsutil cpでgzファイルをコピーしておいてstatic typeでロードしました。こっちはワイルドカード使えて良いですね。

{
  "type": "index_hadoop",
  "spec": {
    "ioConfig" : {
      "type" : "hadoop",
      "inputSpec": {
        "type": "static",
	"paths": "file:/mnt/disks/druid/gcs/*/*.log.gz"
      }
    },
    "dataSchema" : {
      "dataSource" : "all-hadoop",
      "granularitySpec" : {
        "type" : "uniform",
        "segmentGranularity" : "day",
        "queryGranularity" : "none",
	"intervals": ["2018-01-29/2018-02-03"]
      },
      "parser" : {
        "type" : "hadoopyString",
        "parseSpec" : {
          "format" : "json",
          "dimensionsSpec" : {
            "dimensions" : [
              "id",
	      "reqid",
	      "ip",
	      "os"
            ]
          },
          "timestampSpec" : {
            "format" : "auto",
            "column" : "time"
          }
        }
      },
      "metricsSpec" : [
        {
          "name" : "count",
          "type" : "count"
        }
      ]
    },
    "tuningConfig" : {
      "type" : "hadoop",
      "numBackgroundPersistThreads": 1
    }
  }
}

動作を見ていたところLocalTaskRunnerのようなプロセスが1つだけ起動され、1ファイルずつMapReduceが走っていたため高速化は期待できなさそうです。

ちゃんとHadoopクラスタを用意して、DeepStorageもLocalではなくHDFSにすることでスケールできそうです。

 

課題

データロードが遅い

ロード検証の通り、通常のindex typeでは遅すぎると感じました。

index_hadoop typeでGCSからロードしてHDFSをDeepStorageとすればよさそうですが、GCSからのロードは作り込みが必要そうです。(S3はいけそう)

また、Druidクラスタ + 別途Hadoopクラスタを用意するとなるとかなり大掛かりになってしまいます。

1 peon : 1 taskの処理しか行わないようなので、1taskの処理をスケールさせようと思うとMapReduceを利用するのが良さそうです。

 

DeepStorageのロケーション

デフォルトのLocalだと、MiddleManagerホストのディスクにセグメントと呼ばれるデータロード済みのデータが保存されます。

複数台ある場合だとtaskを担当するpeonが動いているノードにのみ保存され、分散されません。

GCS, HDFSをDeepStorageとしないとスケーラビリティがありません。

 

joinができない

残念ながらテーブル間のjoinはサポートされていません。

全結合したデータを1テーブルにロードするような形で実現するのが良さそうですが、データロード時にjoinすることはできませんでした。

updateはDruidでサポートされていますが、特定のkeyのjsonを作ってPOSTする形なので、既存テーブルに対して今回ロードするgzファイルの特定キーを元にupdate(merge)する、というのは実現できなさそうでした。

1つのテーブルに複数ログの全カラムを用意して全データをロードしてみましたが、マージ処理はできないため全レコードがロードされます。

 

可視化

Druidに対応しているSupersetで可視化しました。SupersetにBrokerノードのインターフェースをセットすると、Superset側でDruidメタデータからスキーマ定義など読み込むことができます。

クエリは書けないため、Dimensionでのgroup byができるくらいです。

値を使った演算が必要なものはログに含める or task定義のメトリクスとして記述する必要があり、supersetのGUIからはできませんでした。

まとめ

というわけで、BigQueryによる分析の代替手段としてDruidを検証してみましたが750MB程度のデータロードに7時間程度かかってしまい、Druidクラスタ単体での運用は厳しいと判断せざるを得ませんでした。また、joinもできないため事前にデータ整形が必要となり、今のアーキテクチャでは運用コストが高そうです。

次回はDruidクラスタ + Hadoopクラスタで本格的な大規模データ処理も試してみたいと思っています。

]]>
KubeCon + CloudNativeCon NorthAmerica 2017 @Austin recap https://cyberagent.ai/blog/pr/kubernetes/3925/ Wed, 10 Jan 2018 03:30:53 +0000 https://cyberagent.ai/techblog/?p=3925 こんにちは。

アドテクスタジオ CIA の 青山真也 です。

本日は KubeCon + CloudNativeCon 2017 の話をしたいと思います。
資料はちょこちょこ公開していましたが、ブログを書くのが遅れてしまいました。
にしても、Kubernetes 楽しいですね。

 

KubeCon 2017 の概要

KubeCon + CNCon は半年に1回開催されており、今回は大幅に人数が増え 4000 人満員御礼でした。
Kubernetes が事実上のデファクトスタンダードとなったことにより、注目度も非常に高くなったのだと思います。

CNCF が提供する CloudNative Landscape も 1.0 となり、Fluentd や CoreDNS、その他多くのプロジェクトが 1.0 リリースを発表しました。
これは事実上、CNCF からの CloudNative はもう準備完了というメッセージのようですね。
他にも ServiceMesh を実現する Istio や Conduit、Serverless を実現する Fission、kubeless、OpenFaaS などの注目度が高かった印象です。

今回の会場はテキサス州オースティンだったため、時差的には +0900 から -0600 の差(15 時間の時差)があるため 12/4 は 39 時間ありました。
2018 年は下記の日程で開催予定です。
皆様コペンハーゲンでお会いしましょう。

  • KubeCon + CloudNativeCon Europe 2018
    • コペンハーゲン デンマーク
    • 2018/5/2-4
  • KubeCon + CloudNativeCon China 2018
    • 上海 中国
    • 2018/11/14-15
  • KubeCon + CloudNativeCon North America 2018
    • シアトル アメリカワシントン州
    • 2018/12/11-13

 

Keynote

Keynote では様々な企業での Kubernetes や CNCF がホストしている Cloud Native を促進するプロジェクトの事例が多々紹介されていました。
また、CNCF が保有する各プロジェクトのアップデートやコミュニティの活発さも紹介されており、非常に盛り上がりを見せていました。

社内向けに作成した Keynote 1 日目 + αの資料を挙げてありますので、詳しくはこちらを御覧ください。

 

 

CI/CD 周りのセッション

今回の KubeCon では、「CI/CD 関連」「ServiceMesh(Istio / Conduit)」「Network や OpenStack 周りの SIG」「Serverless(Fission / Kubeless / OpenFaaS)」辺りを中心に聞いてきました。

社内的には CI/CD 周りの改善が望まれていたため、こちらも上記の社内向けに作成した資料に挙げてあります。以下簡単に概要だけ記載しておきます。

 

Using Containers for Continuous Integration and Continuous Delivery

Jenkins と Kubernetes の Integration の話。Groovy で PodTemplate のような記述を行って、build task をコンテナとして起動することができるようです。

 

Deploying to Kubernetes Thousands of Times Per/Day

Kubernetes に対して高速な開発を行なう際に注意すべき4つのポイントの話。具体的かつ体系だって説明がされており、Kubernetes を開発に取り入れる場合には是非見てほしい内容です。

 

Continuous Delivery with Kubernetes at Box

Kubernetes クラスタに Agent を仕込んでおき、Agent が Github 上の Manifest を監視して自身の Kubernetes クラスタを更新する方式で CD を行なう話。新規クラスタに Agent を入れるだけで復旧できるなど、Github をベースとした CD 環境を構築できるため、後術する GitOps などとも近い印象。

 

Microservices, Service Mesh, and CI/CD Pipelines: Making It All Work Together

Istio と BRIGATE (+ kashti)を使った、高度な CI/CD パイプラインを構成する方法。Istio を使うと Canary ・Chaos テスト・Fault Injection・レスポンスタイムなどの集計などができ、BRIGADE を組み合わせると Istio と連携しつつコードによるPipeline 定義をはじめとして、様々なことができるようです。使いこなせたらかなり良い Pipeline 環境にできそうです。

 

Building Better Containers: A Survey of Container Build Tools

Dockerfile では様々な問題があるため、多々ある別のイメージビルドツールの調査結果の話。packer のような方式のイメージビルドツールもあれば、ソースコードから直接イメージを作成するようなツールなど多岐に渡って紹介されていました。

 

GitOps – Operations by Pull Request

GitHub (Pull Request)を利用した Kubernetes 環境への CI/CD パイプラインの作り方の話。非常に分かりやすく、CI/CD パイプラインを考えるなら、このセッションか 2 日目の Kelsey Hightower さんの Keystone 辺りは非常に参考になります。

 

Developer Tooling for Kubernetes Configuration

Kubernetes の Manifest ファイルの Validation と Unit Test を kubeval と kubetest を使って行なう話。CI 時にある程度テストをしておくのは有用かもしれません。

 

Webhooks for Automated Updates

Rancher の Webhook Framework を使って DockerHub の更新時に Kubernetes クラスタの更新を自動化する話。

 

また、先日「@市ヶ谷Geek★Night #16 Kubernetes Christmas!」 でお話させていただいた CI/CD の話では、これらのセッションと CyberAgent での話を混ぜ合わせて CI/CD の話をさせていただきました。

 

 

日本交流会@Austin

今回 KubeCon に参加するにあたり現地で日本交流会を主催させていただきました。著名な方々を含む 33 名もの方々にお集まりいただき、貴重な意見交換ができて良かったです。@tnir さんによると KubeCon 2016 の日本人参加者は 3 名とのことなので、最低でも 11 倍になったようです。非常に盛り上がりを見せていますね。

KubeCon + CloudNativeCon Europe 2018 に行かれる方がいらっしゃいましたら、ぜひコペンハーゲンでお会いしましょう。

 

 

その他

Certified Kubernetes Administrator に合格していると RX-M 様のスポンサーブースで CKA の Tシャツをいただく事ができました。

 

また、KubeCon + CloudNativeCon の多くのセッションは動画が公開されています。興味のあるセッションがあれば Youtube の字幕機能を使いつつ見てみるといいかもしれません。

 

そして KubeCon + CNCon EU 2018 へ

弊社ではオンプレ環境に GKE ライクな Kubernetes 基盤を構築しています。Kubernetes にパッチを宛てて Hardware LB との連携を行ったり Ingress Controller の実装などを行い nginx-ingress / nghttpx-ingress ベースの GKE ライクな Ingress Controller を作成しました。今後はこういった事例で登壇も目指していきたいと思います。

日本からも Kubernetes や Cloud Native を盛り上げていきましょう!

 

 

]]>
[Kubernetes] オンプレでも GKE Like な Ingress を使うために 自作 Ingress Controller を実装してみた https://cyberagent.ai/blog/pr/kubernetes/3758/ Thu, 30 Nov 2017 23:00:54 +0000 https://cyberagent.ai/techblog/?p=3758 こんにちは。
CIA の青山 真也です。

この記事は Kubernetes Advent Calendar 1日目の記事になります。

ちなみに CyberAgent Advent Calndar 1日目の記事も「GKE 互換のオンプレコンテナ基盤 AKE (Adtech Container Engine) 誕生秘話とアーキテクチャ完全公開!」というテーマで書いており、まるで夏休み最終日の気分です。
弊社のオンプレコンテナ基盤 AKE の話を詳細にまとめてありますので、こちらも是非お読み下さい。

今回は、SSL 終端や L7 パスベースルーティング及び L7 ロードバランシングを行ってくれる Ingress リソースの話をします。
GKE の Ingress が最高すぎるんですが、GKE の Ingress に負けない使い勝手になりました!

 

はじめに

みなさんは GKE 等で Ingress リソースを作成したことがある方が多いかと思います。

詳しくは後ほど話しますが、GKE で Ingress リソースを作ると非常に簡単に Ingress リソースを作ることができ、しかも分かりやすいと言った特徴があります。
AWS などでも alb-ingress-controller 等で ALB 連携できる Ingress Controller などが用意されています。

しかし、OpenStack 環境やベアメタル環境などでは Ingress を使う場合には手間が掛かる(しかも初心者には分かりづらい)nginx-ingress-controller や nghttpx-ingress-controller を利用する必要があります。

今回は、OpenStack 環境やベアメタル環境などの Kubernetes でも GKE Like に Ingress を利用できる方法をお伝えできればと思います。

 

Ingress の基礎知識

Ingress は L7 LoadBalancing を行なうリソースです。

たとえばこのような Deployment と、

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample-ingress-apps
spec:
  replicas: 5
  selector:
    matchLabels:
      ingress-app: sample
  template:
    metadata:
      labels:
        ingress-app: sample
    spec:
      containers:
        - name: nginx-container
          image: zembutsu/docker-sample-nginx:1.0
          ports:
            - containerPort: 80

このような Service を事前に作っておきます。

apiVersion: v1
kind: Service
metadata:
  name: svc1
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8888
      targetPort: 80
  selector:
    ingress-app: sample

 

Ingress では NodePort の Service を登録しておく必要があります。

その後、下記のような Ingress リソースを登録することで L7 LoadBalancer が作成され、L7 LoadBalancer から svc1 に対して転送されるような形になります。
余談ですが、spec.backend はデフォルトの転送先になります。また、SSL 終端を行なう場合には spec.tls を設定しておいて下さい。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: sample1.example.com
    http:
      paths:
      - path: /path1
        backend:
          serviceName: svc1
          servicePort: 8888
  backend:
    serviceName: svc1
    servicePort: 8888
  tls:
  - hosts:
    - sample1.example.com
    secretName: tls-sample

 

図にするとこのような感じです。(/path2 宛を svc2 に転送する設定も入っている場合)

仕組みとしては、L7 LoadBalancer から Service に向けて転送するような形になっています。
GKE の場合と Nginx Ingress Controller の場合で結構仕組みはことなりますが、今の時点では結果的には L7 LoadBalancer から Service に向けて転送するということだけ抑えておいて下さい。

 

Ingress と Ingress Controller の違い

Ingress と Ingress Controller という似たような名前が登場してきていますが、これら 2 つは全くの別物です。

  • Ingress
    • Kubernetes のリソースの1種
    • ただの定義にすぎないため登録しただけでは何も起こらない(イメージ的には Custom Resource Definision に近い)
  • Ingress Controller
    • Ingress リソースの定義を読んで L7 ロードバランサを用意する

通常はこの Ingress リソースを登録しても Kubernetes に登録自体はされますが、何も起こりません。

GKE ではマネージドマスターで見えなくなっていますが、デフォルトで Ingress Controller が配備されているため GCLB の L7 LoadBalancer が作成されているだけです。

 

GKE の Ingress

GKE の Ingress の場合、Google Cloud LoadBalancer 上に「特定のパスから全 Kubernetes Node の NodePort へと転送する設定」が登録されます。
また、SSL 終端なども GCLB 上で行われます。

Ingress の場合には DSR 構成ではないため、SNAT と同じようにクライアントの IP Address が欠損してしまいますが、HTTP Header の X-Forwarded-For にクライアントの IP Address があるため、必要に応じて利用して下さい。

 

厳密には、GCLB > NodePort Service > Pod という経路をたどるため、GCLB が Kubernetes Node に割り振った後、Kubernetes Node から Kubernetes Node へと転送される可能性がある点に注意して下さい。
より低レイテンシを求める場合には、NodePort Service を作成する際に spec.externalTrafficPolicy を Local に設定して下さい。
この設定を行った場合には、NodePort Service が受けたトラフィックは必ず自分の Pod へと転送するようになります。
但し注意点が 2 つあります。

1つ目は、Kubernetes Node 上に転送できる Pod が無い場合は 別の Kubernetes Node に転送するといった柔軟な対応はしてくれません。spec.externalTrafficPolicy を Local に設定した場合は、必ず 各ノードに Pod が居るようにするか、NodePort に GCLB の転送設定を書き換えて下さい。

2つ目は、Ingress には関係ありませんが、spec.externalTrafficPolicy を Local にしていても、内部のコンテナから NodePort を叩くと、別の Kubernetes Node にもバランシングされる点に注意して下さい。仕様なのかはわかりませんが、iptables のエントリ的に ClusterIP 相当のチェインに吸い込まれるようです。

さすがは GKE といったところでしょうか。非常に分かりやすく理に適った構成だと思います。

 

Nginx Ingress Controller

Nginx Ingress Controller では、L7 LoadBalancer の代わりに Nginx Pod を作成し、Nginx で SSL 終端やパスベースのルーティングを行います。

また、Nginx Pod を作成すると書きましたが、Nginx Ingress Controller = Nginx Pod となります。そのため、負荷分散を行なう場合には Nginx Ingress Controller (Nginx Pod)を Deployment などでスケールさせることで実現します。
展開方法については詳しくはここでは書きませんが、各リポジトリの examples を見ると良いかと思います。

さらに、GKE の Ingress では/path1 に来たリクエストを svc1 の NodePort に転送していましたが、Nginx Ingress Controller では、/path1 に来たリクエストを直接 Pod に転送してくれるといった違いもあります。

この状態だと Nginx Pod に来たリクエストを分散対象の Pod にL7 LoadBalancing や SSL 終端・パスベースルーティングなどを行ってくれることはわかると思います。

しかし、このままではグローバルなエンドポイントを持たないため、nginx Ingress Controller の Pod に向けた “type LoadBalancer” Service を作成して上げる必要があります。
“type LoadBalancer” が利用できない場合などは、nginx Ingress Controller の Pod に向けた “type NodePort” などを作成することでも代用は可能です。
また、クラスタ内部でしか使わない場合には “type ClusterIP” でも問題ありません。

 

整理すると、L4 LoadBalancer > Nginx Ingress Controller Pod > 各 Pod という経路を辿ります。

 

Nginx Ingress Controller が GKE には勝てない部分

Nginx Ingress の使いづらいポイントはいくつかあります。

前述の通り、エンドポイントは Nginx Ingress Controller Pod への Service を作ることで実現します。
そのため、kubectl get ingress などした場合に、IP Address などは書かれておらず、自分自身で管理しなければなりません。
言い換えるならば、Ingress リソースを作成した後は、その Ingress リソース用に Nginx Ingress Controller Deployment を作成し、Service で紐付けてあげる作業が必要です。
さらに、Ingress 上には Service の IP が登録されるわけではないので、確認したい場合には対応する Service を探さなければならないと言った手間が発生します。

 

例えば、GKE 上ではこのような表示になるのですが、

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS           PORTS     AGE
sample-ingress    sample1.example.com   xxx.xxx.xxx.xxx   80, 443   3h
sample-ingress2   sample2.example.com   xxx.xxx.xxx.xxx   80, 443   3h

 

Nginx Ingress の場合にはこのような表示となってしまい、Address を確認することができません。
これは Nginx Ingress を利用している場合には、Ingress リソースの Status が更新される処理が存在しないためです。

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS   PORTS     AGE
sample-ingress    sample1.example.com             80, 443   3h
sample-ingress2   sample2.example.com             80, 443   3h

 

 

また、GKE の場合には複数の Ingress リソースを作成した場合に、それぞれの Ingress リソース毎に GCLB が作成されます。
これらは別のエンドポイントとなるため分離することが可能です。

 

 

一方で nginx ingress Controller の場合には、Ingress リソースに何も指定しないで複数作成した場合、Ingress Controller はどちらの Ingress リソースの定義も吸収します。したがって、上記の様なことを行おうと思っていたにも関わらず、両方共 エンドポイントを分けることは可能だとしても、中身は全く同じになってしまいます。

 

Nginx Ingress Controller を上記のように分離したい場合には、Ingress Controller と Ingress リソースの紐付けを行ってあげる必要があります。

Ingress Controller 側には –ingress-class HOGE の起動オプションを渡して起動します。また、Ingress リソース側に関しては annotation で kubernetes.io/ingress.class: HOGE を指定して作成します。
この作業をすることにより、Ingress Controller が管理する Ingress リソースを判別することができるようになるため、分離を行なうことが可能です。

 

自作 Ingress Controller

前述のおさらいですが、Nginx Ingress Controller を利用する場合、GKE に比べて大きく 3 つの使い勝手が悪い部分があると考えられます。

1.Ingress リソースの状態を確認しても Status は何も分からない

2.Ingress リソースを作成した後に、Nginx Ingress Controller Pod を立てて Service も作成する必要がある

3.Ingress リソースの分離性を保つために Ingress リソースと Nginx Ingress Controller に識別子を設定する必要がある

 

今回はこの問題を解消するべく、Ingress Controller を自作しました。
GKE の方が良いとは言わせません。オンプレも頑張ってるんだぞー。

Deployment Controller など様々なコントローラ系のソースを参考に作成しました。
Controller 系統を自作する場合には、イベントハンドラの機構などは用意されてはいますが、実装量は結構あるため type LoadBalancer のようにお気軽に作れるものではありませんでした。

イベントハンドラとしては、特定の Kubernetes API Resource に対して「追加」「変更」「削除」の処理が行われたタイミングで呼ばれます。
Deployment Controller であれば、追加時に ReplicaSet の追加を行なう・変更時に ReplicaSet から ReplicaSet へのローリングアップデートを実施、削除時は自身の保持する ReplcaSet を削除するといった処理を行なう必要があります。

cache.ResourceEventHandlerFuncs{
                        AddFunc:    c.onAdd,
                        UpdateFunc: c.onUpdate,
                        DeleteFunc: c.onDelete,
                })

 

また、各関数には追加削除時はそのリソースの情報、変更時は変更前後のリソースの情報が渡ってくる形になります。
今回の GKE Like な Ingress Controller では この obj interfece{} は obj.(*ext.Ingress) として Ingress リソースとして扱うことができる構造体として渡ってきています。

func (c *GKELikeIngressController) onAdd(obj interface{}) {
func (c *GKELikeIngressController) onUpdate(oldObj, newObj interface{}) {
func (c *GKELikeIngressController) onDelete(obj interface{}) {

 

GKE Like な自作 Ingress Controller の仕組みはわりとシンプルな構成になっており、Nginx Ingress Controller を上記のように手動で使う部分を全て自動化する Controller として実装しています。

onAdd が呼び出された際には、下記の 4 つの工程を行います。

  • Ingress リソースの annotation に “kubernetes.io/ingress.class” = NAMESPACE_INGRESSNAME を埋め込む
  • Ingress リソースに対応する Ingress Controller の Deployment を作成(–ingress-class = NAMESPACE_INGRESSNAME)
  • 上記の Deployment に対する “type Loadbalancer” Service を作成
  • Ingress リソースの Status に LB の IP と ClusterIP を反映

リソースの作成には kubernetes/go-client を利用しています。
例えば、Ingress の場合にはここにCreate()、Delete()、Update()、UpdateState() といったインターフェースが定義されています。

また、全てのリソースに共通することですが、onAdd 関数内で複数回リソースを更新するとエラーが発生するため、1回だけにする必要があります。
さらに、各リソースの Status に関しては Update() 関数ではなく UpdateStatus() 関数を利用しないと更新があっても無視される点に注意して下さい。
今回の場合だと、Ingress リソースの Status.LoadBalancer.Ingress 構造体を更新することで、LoadBalancer の IP と Cluster IP を登録しています。

onUpdate が呼び出された場合には特別な作業は行いません。作り込み次第ですが、 IP 変更時の変更処理程度だと思います。
Ingress リソースの更新に関しては、Nginx Ingress Controller の Deployment が変更を反映してくれるため、この部分に複雑な処理が必要ないのは多いなメリットだと思います。

onDelete 時には Deployment と Service を削除します。

 

以上の実装をすることで、GKE Like な Ingress Controller を作成することが出来ました。
GKE と違う部分としては、Internal から Ingress を叩く場合には ClusterIP を利用したほうが良いため、Ingress の IP にも一応入れています。

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS                      PORTS     AGE
sample-ingress    sample1.example.com   192.104.167.215,10.10.10.45   80, 443   3h
sample-ingress2   sample2.example.com   192.111.123.71,10.10.10.47    80, 443   3h

 

裏側的には Deployment と Service が配置されています。当然 annotation と ingress-class は指定された状態でデプロイされています。

# kubectl -n kube-system get deploy,service sample-ingress
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/sample-ingress   1         1         1            1           3h

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
svc/sample-ingress   LoadBalancer   192.104.167.215   10.10.10.45   80:11652/TCP,443:44404/TCP   3h

# kubectl -n kube-system get deploy,service sample-ingress2
NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/sample-ingress2   1         1         1            1           3h

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
svc/sample-ingress2   LoadBalancer   192.111.123.71   10.10.10.47   80:32535/TCP,443:33424/TCP   3h

 

物理 Ingress Controller

Nginx Ingress Controller を利用する形で GKE ライクな Ingress Controller を自作しましたが、結局のところパフォーマンスを最大化するには L7 を捌ける Hardware LoadBalancer が欲しいなと思います。
現状の弊社の LoadBalancer では L7 を全て捌き切るのば難しいため、実装を見送りましたが、Hardware LoadBalancer を使った Ingress も実現することは容易なため、機会や利用感を見つつ検討したいと思っています。

 

Nginx / Nghttpx Ingress Controller の違い

Nginx を利用しているか Nghttpx を利用しているかの違いがありますが、Nghttpx Ingress Controller は Nginx Ingress Controller を参考に作られているため、基本的には同じだと思います。
Nghttpx の方は ZLab 様が公開してくれており、HTTP/2 に対応しているため、gRPC にも利用できるといった特徴があります。
また、今回実装した GKE Like Ingress Controller はどちらも選択できるようにしています。

 

以上、Ingress Controller を作ってみたお話でした。
KubeCon から帰ってきたら、じきに OSS で公開しようかな―と思っています。

]]>
アジャイル診断と自動脆弱性診断ツールの活用(OWASP ZAP編) https://cyberagent.ai/blog/tech/oss/3630/ Mon, 20 Nov 2017 04:52:53 +0000 https://cyberagent.ai/techblog/?p=3630 はじめに

こんにちは、アドテクスタジオでセキュリティエンジニアをしている岡崎です。

前回は、「Google ChromeにおけるSymantec系サーバ証明書の扱いについて」書かせていただきました。
今回は、「アジャイル診断と自動脆弱性診断ツールの活用(OWASP ZAP編)」について、紹介させていただきます。

近年、東京オリンピックも近いことから日本のシステムは多くのサイバー攻撃を受けてます。このようなサイバー攻撃からアドテクスタジオのシステムを守る手段として、以前から脆弱性診断を取り入れてます。最近では脆弱性診断も内部に脆弱性診断チームを持ち、内製化をしました。その為、さらに脆弱性診断が受診しやすくなりました。
とはいえ、脆弱性診断を行う際は、プロダクト(開発側)に以下のようなヒアリングや診断中の対応に貴重な時間を割いてもらわなければなりません。

 

[ヒアリング例]
・脆弱性診断対象があるデータセンターはどちらになるでしょうか。
・脆弱性診断対象のURLやアカウント情報ををください。
・脆弱性診断チームから脆弱性診断対象への接続元IPを許可してください。
・AWSアカウントの場合は、AWSへの侵入テスト申請をお願いします。
・診断日はX月X日からX日間でよろしいでしょうか。
・加えて、診断当日の機能やエラーに対するお問い合わせ
など

セキュリティチームが中心となりヒアリングや調整を行なってますが、もっとスムーズに手軽に脆弱性診断を受けてもらえる仕組みがないか、考え自動脆弱性スキャンツールの導入を検討しました。

脆弱性診断は、新規システム構築時(リニューアル含む)と年に1回の定期診断を最低限としていますが、セキュリティチームとしては、小さな細々とした機能修正や機能追加時に対しても都度行いたいものです・・・
また、アドテクスタジオでは多くのプロダクト(サービス・システム)が存在します。各々の機能修正や機能追加の度に脆弱性診断の調整をしているとあっというまに1年が終わってしまいます。

このような問題を解決するためにも自動脆弱性診断ツールを活用できないか、と思いました。

 

自動脆弱性診断ツールの導入

脆弱性診断を都度行ってしまうと、プロダクト側の貴重な開発時間を奪ってしまうため、開発スピードを鈍化させる恐れがあります。
アドテクスタジオのセキュリティチームでは、「セキュリティ強化することによる開発や売上の鈍化」に関して敏感であります。
そのため、第三による脆弱性診断は適度な回数にし、その間のフォローとして「自動脆弱性診断ツール」を活用することにしました。

 

アジャイル診断

日々の開発フローの中にテストの一環として、自動脆弱性診断ツールを活用する試みになります。

 

このように開発フローに組み込むことにより、

日々行われる修正や機能追加は自動脆弱性診断ツールを活用し、ある程度まとまった段階で第三者による脆弱性診断を受けるようにすることで、開発側の脆弱性診断対応コストをなるべく減らし、セキュリティ強化することが可能です。

 

脆弱性自動診断ツールと脆弱性診断の差

脆弱性自動診断ツールでは、もちろん発見可能な脆弱性は限りがあります。

[脆弱性自動診断ツールで発見可能な脆弱性]

可能:
・OSコマンドインジェクション
・SQLインジェクション
・クロスサイトスクリプティング(XSS)
・クロスサイトリクエストフォージェリ(CSRF)
・バージョン情報の漏洩
・HTTPヘッダー系の不備
など

 

困難:
・不適切な認証及びパスワードリセット機能
・セッション管理周り
・アクセスコントロールの不備
・エラー処理の不備
・アプリケーションロジックの不備
・その他、不正行為
など

このような違いを考慮して、利用していきます。

 

続いては、どのような仕組みになっているかを説明します。

 

OWASP ZAPの活用

今回、脆弱性診断ツールとしては、OWASP ZAPを利用しました。OWASP ZAPでスキャンした結果をKibanaを利用し、出力しています。

*OWASP ZAPとはOWASPが提供しているOSSになります。
https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project

 

OWASP ZAPとKibanaの連携図

 

今回は、shellで活用したいため、LinuxにOWASP ZAPをインストールする手順を紹介します。

 

インストール手順

# 必要なパッケージのインストール
yum install wget git screen tree  openssl net-tools unzip jq libxml2* nkf

# Java インストール
yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
export PATH=$PATH:$JAVA_HOME/bin
java -version
javac

# OWASP ZAPのインストール
cat installZAP.sh
ーーーーー
#!/bin/bash

latest_url=`curl -s https://raw.githubusercontent.com/zaproxy/zap-admin/master/ZapVersions.xml|xmllint --xpath "/ZAP/core/linux/url" - | sed "s/<url>\(.*\)<\/url>/\1/"`
latest_version=`curl -s https://raw.githubusercontent.com/zaproxy/zap-admin/master/ZapVersions.xml|xmllint --xpath "/ZAP/core/version" - | sed "s/<version>\(.*\)<\/version>/\1/"`
latest_file=`echo ${latest_url}| awk -F / '{print $NF}'`
wget ${latest_url}
tar zxvf ${latest_file}
mv ZAP_${latest_version} ~/zap
rm ${latest_file}

echo "ZAP Install Complete"
ーーーーー

sh installZAP.sh


tree -L 1 zap
zap
├── db
├── lang
├── lib
├── license
├── plugin
├── README
├── scripts
├── xml
├── zap-2.6.0.jar
├── zap.bat
├── zap.ico
└── zap.sh

#参考:
#https://raw.githubusercontent.com/zaproxy/zap-admin/master/ZapVersions.xml


# OWASP ZAPプラグインのインストール
sh zap/zap.sh -cmd -addoninstall ascanrulesAlpha
sh zap/zap.sh -cmd -addoninstall ascanrulesBeta
sh zap/zap.sh -cmd -addoninstall sqliplugin
sh zap/zap.sh -cmd -addonupdate

# ZAPの起動
/bin/bash /root/zap/zap.sh -daemon -port 8090 -config api.disablekey=true

これでインストールは完了です。

 

次に対象へのスキャンです。
*今回は認証を必要としないスキャンの例です。

#変数
scan_target="http://localhost/"
zap_url="http://localhost:8090"

# spiderの実行
curl -s "${zap_url}/JSON/spider/action/scan/?url=${scan_target}&maxChildren=10&recurse=true"

# scanの実行
curl -s "${zap_url}/JSON/ascan/action/scan/?url=${scan_target}&maxChildren=10&recurse=true"

#結果
# spider
curl -s "${zap_url}/JSON/core/view/urls/"

#scan - json
curl -s "${zap_url}/JSON/core/view/alerts/?baseurl=${scan_target}"

#scan - xml
curl -s "${zap_url}/OTHER/core/other/xmlreport/"

#scan - html
curl -s "${zap_url}/OTHER/core/other/htmlreport/"

参考: https://github.com/zaproxy/zaproxy/wiki/ApiGen_Full

 

OWSP ZAPはそのままhtmlでも結果を出力することができます。

 

Kibanaでの表示

 

おわりに

「アジャイル診断と自動脆弱性診断ツールの活用(OWASP ZAP編)」について、まとめさせていただきました。

アドテクスタジオでは、簡潔にセキュリティ強化できるものを求められてます。

今後もエンジニアが開発しやすく かつ セキュリティが向上しやすい 環境作りに努めて行きます。

*脆弱性スキャンは許可なく行うと、攻撃とみなされるので注意しましょう。

 

]]>
Dockerにおけるセキュリティポイント(補足) https://cyberagent.ai/blog/tech/oss/10207/ Wed, 15 Nov 2017 09:48:31 +0000 https://cyberagent.ai/techblog/?p=3661 はじめに

こんにちは、アドテクスタジオでセキュリティエンジニアをしている岡崎です。

先日、 「11/14 サイバーエージェントとさくらインターネットのインフラ談義」 のLT枠に参加させていただき、以下の資料の発表をさせていただきました。

「Dockerを始めて1週間のセキュリティエンジニア」

LT枠では10分ほどしか時間なく、とても緊張し、お話できなかった内容を追加して書かせていただきます。

 

最近、アドテクスタジオではDockerやk8sなどのコンテナ技術を利用しているプロダクト・システムが数多くあります。

そのなかで、セキュリティエンジニアとしてコンテナ技術を活用するときにどのような点に注意する必要があるのかを、Dockerを試しながら洗い出してみました。

 

今回のDockerのアーキテクチャからセキュリティポイントとして着目したのは、以下の点になります。

・クライアントとデーモン間

 ー セキュアな通信

 ー 接続制御

・レジストリ

 ー セキュアな通信

 ー 接続制御

・イメージ

 ー イメージの信頼性

・コンテナ

 ー コンテナの信頼性

 ー システムコールの制御

 

参考: http://docs.docker.jp/engine/introduction/understanding-docker.html

 

・クライアントとデーモン間

と

・レジストリ

の

 ー セキュアな通信

 ー 接続制御

に関しては、TLS証明書を用いた「SSH公開鍵認証」や「WebサーバのSSL/TLS証明書」と同じ原理になります。

通信の暗号化はもちろんのこと、デーモンやレジストリにアクセスできるユーザを制限してセキュアに使いましょう。

 

・イメージ

 ー イメージの信頼性

に関しては、イメージ上で利用しているミドルウェアなどのパッケージに脆弱性が含まれているバージョンが利用されていないかをチェックすることができます。

Docker Hub(エンタープライズ版)には「Docker Security Scanning」という機能があり、Docker Hubにイメージをアップしておくと、定期的にCVE(Common Vulnerabilities and Exposures:脆弱性情報データベース)の情報を元に脆弱性チェックを行ってくれます。

参考: https://success.docker.com/article/Docker_Reference_Architecture-_Securing_Docker_EE_and_Security_Best_Practices

 

「Docker Hub」で公開されているイメージの中で、「Official」マークのついているものに関しては、イメージスキャン済のものなので、安心して利用できます。

参考: https://hub.docker.com/explore/

 

・コンテナ

 ー コンテナの信頼性

 ー システムコールの制御

に関しては、docker run時のオプションがあり、詳細なセキュリティ設定をすることが可能です。

[コンテナの信頼性]

イメージスキャンに関しては、docker runしていない状態での静的スキャンになりますが、以下で説明します「Docker Bench for Security」に関しては、コンテナに対し以下の項目でセキュリティチェックをしてくれます。

#実行方法
$ sudo docker run -it --net host --pid host --cap-add audit_control \
   -v /var/lib:/var/lib \
   -v /var/run/docker.sock:/var/run/docker.sock \
   -v /usr/lib/systemd:/usr/lib/systemd \
   -v /etc:/etc --label docker-bench-security \
   diogomonica/docker-bench-security

参考: https://github.com/docker/docker-bench-security

*コンテナがあるインスタンス内で実行しないといけないのが難点です

#チェック項目:
[INFO] 1 - Host Configuration
ホストに対しての設定確認

[INFO] 2 - Docker Daemon Configuration
[INFO] 3 - Docker Daemon Configuration Files
デーモンに関しての設定確認

[INFO] 4 - Container Images and Build Files
[INFO] 5 - Container Runtime
コンテナに対しての設定確認

[INFO] 6 - Docker Security Operations

 

#実行結果
docker run -it --net host --pid host --cap-add audit_control \
>     -v /var/lib:/var/lib \
>     -v /var/run/docker.sock:/var/run/docker.sock \
>     -v /usr/lib/systemd:/usr/lib/systemd \
>     -v /etc:/etc --label docker-bench-security \
>     diogomonica/docker-bench-security

# ------------------------------------------------------------------------------
# Docker Bench for Security v1.0.0
#
# Docker, Inc. (c) 2015
#
# Checks for dozens of common best-practices around deploying Docker containers in production.
# Inspired by the CIS Docker 1.6 Benchmark:
# https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf
# ------------------------------------------------------------------------------

Initializing Wed Nov 15 03:20:26 GMT 2017

[INFO] 1 - Host Configuration
[WARN] 1.1  - Create a separate partition for containers
[PASS] 1.2  - Use an updated Linux Kernel
[PASS] 1.5  - Remove all non-essential services from the host - Network
[PASS] 1.6  - Keep Docker up to date
[INFO] 1.7  - Only allow trusted users to control Docker daemon
[INFO]      * docker:x:993:
[WARN] 1.8  - Failed to inspect: auditctl command not found.
[WARN] 1.9  - Failed to inspect: auditctl command not found.
[WARN] 1.10 - Failed to inspect: auditctl command not found.
[INFO] 1.11 - Audit Docker files and directories - docker-registry.service
[INFO]      * File not found
[WARN] 1.12 - Failed to inspect: auditctl command not found.
[WARN] 1.13 - Failed to inspect: auditctl command not found.
[INFO] 1.14 - Audit Docker files and directories - /etc/sysconfig/docker
[INFO]      * File not found
[INFO] 1.15 - Audit Docker files and directories - /etc/sysconfig/docker-network
[INFO]      * File not found
[INFO] 1.16 - Audit Docker files and directories - /etc/sysconfig/docker-registry
[INFO]      * File not found
[INFO] 1.17 - Audit Docker files and directories - /etc/sysconfig/docker-storage
[INFO]      * File not found
[INFO] 1.18 - Audit Docker files and directories - /etc/default/docker
[INFO]      * File not found


[INFO] 2 - Docker Daemon Configuration
[PASS] 2.1  - Do not use lxc execution driver
[WARN] 2.2  - Restrict network traffic between containers
[PASS] 2.3  - Set the logging level
[PASS] 2.4  - Allow Docker to make changes to iptables
[PASS] 2.5  - Do not use insecure registries
[INFO] 2.6  - Setup a local registry mirror
[INFO]      * No local registry currently configured
[PASS] 2.7  - Do not use the aufs storage driver
[PASS] 2.8  - Do not bind Docker to another IP/Port or a Unix socket
[INFO] 2.9  - Configure TLS authentication for Docker daemon
[INFO]      * Docker daemon not listening on TCP
[INFO] 2.10 - Set default ulimit as appropriate
[INFO]      * Default ulimit doesn't appear to be set


[INFO] 3 - Docker Daemon Configuration Files
[PASS] 3.1  - Verify that docker.service file ownership is set to root:root
[PASS] 3.2  - Verify that docker.service file permissions are set to 644
[INFO] 3.3  - Verify that docker-registry.service file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.4  - Verify that docker-registry.service file permissions are set to 644
[INFO]      * File not found
[INFO] 3.5  - Verify that docker.socket file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.6  - Verify that docker.socket file permissions are set to 644
[INFO]      * File not found
[INFO] 3.7  - Verify that Docker environment file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.8  - Verify that Docker environment file permissions are set to 644
[INFO]      * File not found
[INFO] 3.9  - Verify that docker-network environment file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.10 - Verify that docker-network environment file permissions are set to 644
[INFO]      * File not found
[INFO] 3.11 - Verify that docker-registry environment file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.12 - Verify that docker-registry environment file permissions are set to 644
[INFO]      * File not found
[INFO] 3.13 - Verify that docker-storage environment file ownership is set to root:root
[INFO]      * File not found
[INFO] 3.14 - Verify that docker-storage environment file permissions are set to 644
[INFO]      * File not found
[PASS] 3.15 - Verify that /etc/docker directory ownership is set to root:root
[PASS] 3.16 - Verify that /etc/docker directory permissions are set to 755
[INFO] 3.17 - Verify that registry certificate file ownership is set to root:root
[INFO]      * Directory not found
[INFO] 3.18 - Verify that registry certificate file permissions are set to 444
[INFO]      * Directory not found
[INFO] 3.19 - Verify that TLS CA certificate file ownership is set to root:root
[INFO]      * No TLS CA certificate found
[INFO] 3.20 - Verify that TLS CA certificate file permissions are set to 444
[INFO]      * No TLS CA certificate found
[INFO] 3.21 - Verify that Docker server certificate file ownership is set to root:root
[INFO]      * No TLS Server certificate found
[INFO] 3.22 - Verify that Docker server certificate file permissions are set to 444
[INFO]      * No TLS Server certificate found
[INFO] 3.23 - Verify that Docker server key file ownership is set to root:root
[INFO]      * No TLS Key found
[INFO] 3.24 - Verify that Docker server key file permissions are set to 400
[INFO]      * No TLS Key found
[INFO] 3.25 - Verify that Docker socket file ownership is set to root:docker
[INFO]      * File not found
[INFO] 3.26 - Verify that Docker socket file permissions are set to 660
[INFO]      * File not found


[INFO] 4 - Container Images and Build Files
[WARN] 4.1  - Create a user for the container
[WARN]      * Running as root: eloquent_booth
[WARN]      * Running as root: serene_babbage


[INFO] 5  - Container Runtime
[WARN] 5.1  - Verify AppArmor Profile, if applicable
[WARN]      * No AppArmorProfile Found: eloquent_booth
[WARN]      * No AppArmorProfile Found: serene_babbage
[WARN] 5.2  - Verify SELinux security options, if applicable
[WARN]      * No SecurityOptions Found: serene_babbage
[WARN] 5.3  - Verify that containers are running only a single main process
[WARN]      * Too many proccesses running: eloquent_booth
[WARN]      * Too many proccesses running: serene_babbage
[WARN] 5.4  - Restrict Linux Kernel Capabilities within containers
[WARN]      * Capabilities added: CapAdd=[audit_control] to eloquent_booth
[PASS] 5.5  - Do not use privileged containers
[WARN] 5.6  - Do not mount sensitive host system directories on containers
[WARN]      * Sensitive directory /etc mounted in: eloquent_booth
[WARN]      * Sensitive directory /lib mounted in: eloquent_booth
[WARN] 5.7  - Do not run ssh within containers
[WARN]      * Container running sshd: eloquent_booth
[WARN] 5.8  - Do not map privileged ports within containers
[WARN]      * Privileged Port in use: 80 in serene_babbage
[WARN] 5.10 - Do not use host network mode on container
[WARN]      * Container running with networking mode 'host': eloquent_booth
[WARN] 5.11 - Limit memory usage for container
[WARN]      * Container running without memory restrictions: eloquent_booth
[WARN]      * Container running without memory restrictions: serene_babbage
[WARN] 5.12 - Set container CPU priority appropriately
[WARN]      * Container running without CPU restrictions: eloquent_booth
[WARN]      * Container running without CPU restrictions: serene_babbage
[WARN] 5.13 - Mount container's root filesystem as read only
[WARN]      * Container running with root FS mounted R/W: eloquent_booth
[WARN]      * Container running with root FS mounted R/W: serene_babbage
[WARN] 5.14 - Bind incoming container traffic to a specific host interface
[WARN]      * Port being bound to wildcard IP: 0.0.0.0 in serene_babbage
[PASS] 5.15 - Do not set the 'on-failure' container restart policy to always
[WARN] 5.16 - Do not share the host's process namespace
[WARN]      * Host PID namespace being shared with: eloquent_booth
[PASS] 5.17 - Do not share the host's IPC namespace
[PASS] 5.18 - Do not directly expose host devices to containers
[INFO] 5.19 - Override default ulimit at runtime only if needed
[INFO]      * Container no default ulimit override: eloquent_booth
[INFO]      * Container no default ulimit override: serene_babbage


[INFO] 6  - Docker Security Operations
[INFO] 6.5 - Use a centralized and remote log collection service
[INFO]      * Container has no volumes, ensure centralized logging is enabled : serene_babbage
[INFO] 6.6 - Avoid image sprawl
[INFO]      * There are currently: 26 images
[WARN]      * Only 8 out of 26 are in use
[INFO] 6.7 - Avoid container sprawl
[INFO]      * There are currently a total of 8 containers, with 2 of them currently running

参考: https://github.com/docker/docker-bench-security

わかりやすいところですと「rootユーザで実行している」と指摘されているのがわかります。

[INFO] 4 - Container Images and Build Files
[WARN] 4.1  - Create a user for the container
[WARN]      * Running as root: eloquent_booth
[WARN]      * Running as root: serene_babbage

この基準は、「Understanding Docker Security and Best Practices」にて定義されているものになるようです。熟読しておきましょう。

参考: https://blog.docker.com/2015/05/understanding-docker-security-and-best-practices/

 

[システムコールの制御]

docker run時のオプションに「–security-opt」というものがあります。

#AppArmorが有効なホスト上
--security-opt=apparmor:$domain

#SELINUXが有効なホスト上
--security-opt=label:user:$userID
--security-opt=label:role:$role
--security-opt=label:type:$type
--security-opt=label:level:$level
--security-opt=label:disable

のようなオプションを追加することで、MAC(Mandatory Access Control:強制アクセス制御)を実現できます。

また、「seccomp(secure computing mode)」オプションを利用することで、コンテナ上で利用可能なシステムコールを細かく制御することもできます。

--security-opt seccomp:$profile

#利用するには以下の項目が「y」になっていること
$ grep SECCOMP /boot/config-$(uname -r)
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
CONFIG_SECCOMP_FILTER=y
CONFIG_SECCOMP=y


#default sampleの実行
#https://github.com/docker/labs/blob/master/security/seccomp/seccomp-profiles/default.json
$ docker container run --rm -it --security-opt seccomp=default.json alpine sh
/ # chmod 777 / -v
mode of '/' changed to 0777 (rwxrwxrwx)

#chmod禁止パターン sampleの実行
#https://github.com/docker/labs/blob/master/security/seccomp/seccomp-profiles/default-no-chmod.json
#以下の3つのシステムコールが削除されていることがわかります。
ーーーーー
- {
- "name": "chmod",
- "action": "SCMP_ACT_ALLOW",
- "args": []
- },
- {
- "name": "fchmod",
- "action": "SCMP_ACT_ALLOW",
- "args": []
- },
- {
- "name": "fchmodat",
- "action": "SCMP_ACT_ALLOW",
- "args": []
- },
ーーーーー

$ docker container run --rm -it --security-opt seccomp=default-no-chmod.json alpine sh
/ # chmod 777 / -v
chmod: /: Operation not permitted

参考: https://github.com/docker/labs/tree/master/security/seccomp
https://docs.docker.com/engine/security/seccomp/

 

おわりに

Dockerのセキュリティ周りはこれからどんどん熱くなってくると思うので、今後いろいろ試し、アドテクスタジオのセキュリティ強化のために活用できる仕組みを考えていこうと思っています。

]]>
オープンソースライブラリbreezeを導入したときちょっと困ったこと https://cyberagent.ai/blog/tech/scala/3297/ Mon, 22 Dec 2014 02:44:28 +0000 http://cyberagent.ai/scalablog/?p=327 こんにちは、AMoAdの清水です。AMoAdでは広告配信ネットワークを提供しており、その配信システムを最適化するために広告毎のCTR(click率), CPM(1000回表示あたりの課金額)やimpression数(広告表示回数:imp)など様々な予測を行っています。
予測バッチ他、多くのバッチをAMoAdではScalaで実装しています。
なぜScalaを採用しているのか、それは

  • 元々のシステムのコードはJavaで書かれていたため資産を引き継ぎやすい
  • Javaよりコードが簡潔に書くことができ、可読性/保守性が高くなる
  • Scala大好き

という理由です^^

今日は予測バッチにScalaで行列計算ができるオープンソースライブラリ「scalanlp/breeze」を導入してみてちょっと困った話を紹介したいと思います!

予測モデルについてざっくりと

 

まず、一定期間のデータを元に重回帰分析を用いて予測モデルを作りますが
例えばZの値を予測したい場合、Zに相関のあるA, B, C, D,… といった値とベクトルβを掛けて計算します。

z
Z = impとして予測する場合、A, B, C, D,…にはimpに相関関係のあると思われる指標を入れるというイメージです。
相関しそうなものなんてCPMとかCTRとか色々ありますがそこは工夫次第…。

nは当日をn = 0, 前日をn = 1のように表した値です。
ここまでは特別なことではありませんね。
秘伝のタレβをどれだけ上手に作るかが予測精度に大きく関係してきます。
予測モデルの話をしたいわけではないのでこの辺はさっと飛ばします。というかできません笑

ではβを求めます。

beta
X:過去のimpやCPMやCTRやイベント変数等の行列
Y:impやCPMのベクトル
これをバッチ化して計算するのは非常に大変です。
まずXの行列のデータを何で持つか、二次元配列?List?
転置と逆行列に相当する関数を自分で実装するのは非常にめんどくさい上にバグの保証はどうするのかと…?

そんなときに便利なのが冒頭でも説明したScalaで行列が書けてしまうオープンソースライブラリ、ScalaNLPの「breeze」です!

基本的な使い方はgithubのドキュメントとこちらのブログで勉強しました。(ブログ主様にリンク許可は頂きました)

ScalaでRやMatlabみたいに行列の操作をおこなう

 

 

ではbreezeで実装していきましょう

 breezeのバージョンはv0.9を使用

式はもうあるのであとは実装していくだけです。

XとYにDBから取ってきた値を詰めていざ計算してみます^ω^

(めっちゃ端折ってますが笑)

 

val X: DenseMatrix[Double] = ...
val Y: DenseMatrix[Double] = ...
val beta = inv(X.t * X) * X.t * Y

// X.tはXの転置行列
// invは逆行列を求める関数

 

結果:MatrixSingularException

あれ??

原因を考えると、日によって行列Xに縦に0が並ぶことがある -> 1行全部0は逆行列の定義的に計算できない!
はい。高校のときに習ったやつを思い出します。

 

|a b|
|c d|

2 × 2 の正則行列で逆行列の存在条件は「 ad-bc ≠ 0 」でした。

4 × 4 でも10 × 10でも縦に0が並んだら存在条件を満たさなくなってしまいます。
よってエラるのは納得です^ω^

そうゆうわけで疑似逆行列を使いたいところですが幸いなことにbreezeにはムーア・ペンローズの疑似逆行列関数も実装されていました。
pinvってやつです。

では早速…

 

val beta = pinv(X.t * X) * X.t * Y

結果:MatrixSingularException

あ?(^ω^#)

色々試してダメなので簡単にpinvの検証をすることにします。

 

val m = DenseMatrix((0d,3d,6d), (0d,4d,7d), (0d,5d,9d))
val mi = pinv(m)
println(mi)

結果は同じく MatrixSingularException

これが既にだめですね。

一旦、本来期待する値をRの疑似逆行列関数ginvで出してみましょう

 

> m <- rbind(c(0,3,6), c(0,4,7), c(0,5,9))
> ginv(m)
[,1] [,2] [,3]
[1,] 0.000000 0.0000000 0.0000000
[2,] -2.526316 1.4210526 0.5789474
[3,] 1.421053 -0.7368421 -0.2631579

これですよこれ。

なぜRなら計算できるのにbreezeだと計算できないのか?
それぞれの実装を見てみました。

R(3.1.0)の実装 (関数実装のコンソール出力より)

> ginv
function (X, tol = sqrt(.Machine$double.eps)) {
 if (length(dim(X)) > 2L || !(is.numeric(X) || is.complex(X))) 
   stop("'X' must be a numeric or complex matrix")
 if (!is.matrix(X)) 
   X <- as.matrix(X)
 Xsvd <- svd(X)
 if (is.complex(X)) 
   Xsvd$u <- Conj(Xsvd$u)
 Positive <- Xsvd$d > max(tol * Xsvd$d[1L], 0)
 if (all(Positive)) 
   Xsvd$v %*% (1/Xsvd$d * t(Xsvd$u))
 else if (!any(Positive)) 
   array(0, dim(X)[2L:1L])
 else 
   Xsvd$v[, Positive, drop = FALSE] %*% ((1/Xsvd$d[Positive]) * t(Xsvd$u[, Positive, drop = FALSE]))
}

 

breezeの実装 (doc略)
math/src/main/scala/breeze/linalg/functions/inv.scala: 54

object pinv extends UFunc {
  implicit def implFromTransposeAndSolve[T, TransT, MulRes, Result]
  (implicit numericT: T=>NumericOps[T],
   trans: CanTranspose[T, TransT],
   numericTrans: TransT => NumericOps[TransT],
   mul: OpMulMatrix.Impl2[TransT, T, MulRes],
   numericMulRes: MulRes => NumericOps[MulRes],
   solve : OpSolveMatrixBy.Impl2[MulRes, TransT, Result]):Impl[T, Result] = {
    new Impl[T, Result] {
      def apply(X: T): Result = {
        (X.t * X) \ X.t
      }
    }
  }
}

 

(X.t * X) \ X.t
あれ、なんだこれ?

公式ドキュメントに
「Breeze is a generic, clean and powerful Scala numerical processing library patterned after NumPy, Matlab and R and licensed under Apache Public License 2.0.」
て書いてあったのでてっきりbreezeはRやNumPyの機能を踏襲して作ってあるのかと思い込んでいました!
仮にも一部では有名なOSSでありプロダクションコードで実用されている例もいくつもあったので。。

これは僕のような一般人にはわからない思想の違いがあるのかと思い、breezeのメインコントリビューターのDavidさんに聞いてみたところ、
頂いた回答は本来は特異値分解(SVD)を使って実装しなければならない、修正する必要がある、との事でした。
つまりぶっちゃけpinvはほぼ未実装です。
またpinvだけでなく、breeze自体結構バグや未実装機能が多く存在するので使用するときは注意が必要です。

そうですよね、オープンソースってそうゆうことですよね…
そもそもまだバージョン1.0に達してませんし。

また機会があればbreezeで便利な機能やバグにあたるので注意すべきところを紹介したいと思います。(需要ないか…)

 

 

ということで詰みました

 

このままでは行列計算が使えません!
しかしbreezeをやめるのはもっと時間が掛かります。

よって一番手っ取り早い方法としてRのginvを自分で実装してしまえばいいわけです^ω^
ginvの実装はわかっているので簡単ですね!中学生でも書けちゃいますね!!

* 実装してみた結果

object ginv {
  def apply(m: DenseMatrix[Double]) = {
    //特異値分解
    val svd1 = svd(m)
    val u = svd1._1
    val d = svd1._2
    //Rでは特異値分解のvはbreezeのvの転置になっているため転置行列を取る
    val v = svd1._3.t
    //RのDoubleの最小の非負値浮動小数の平方根
    val tol = 1.490116E-08
    val positive = d.map(_ > max(tol * d(0), 0d))
    if (all(positive)) v * multipleVector(d.map(1 / _), u.t)
    else if (!any(positive)) m := 0d
    else dropMatrixColumns(v,positive) * multipleVector(dropVectorColumns(d, positive).map(1 / _), dropMatrixColumns(u, positive).t)
  }

  /**
   * ベクトル[positive]のfalseに対応する行列[dm]の列を削除する 
   * @param dm 対象行列
   * @param positive booleanのベクトル
   * @return DenseMatrix
   */
  def dropMatrixColumns(dm: DenseMatrix[Double], positive: DenseVector[Boolean]): DenseMatrix[Double] = {
    def go(dm2: DenseMatrix[Double], n: Int): DenseMatrix[Double] = {
      if (n <= 0 || positive(n)) dm2
      else go(dm2.delete(n, Axis._1), n - 1)
    }
    go(dm, positive.length - 1)
  }

  /**
   * ベクトル[positive]のfalseに対応するベクトル[dv]の要素を削除する
   * @param dv 対象ベクトル
   * @param positive booleanのベクトル
   * @return DenseVector
   */
  def dropVectorColumns(dv: DenseVector[Double], positive: DenseVector[Boolean]): DenseVector[Double] = {
    val list = dv.toArray
    val lb = ListBuffer(list: _*)
    for (i <- (list.length - 1) to 0 by -1) {
      if (!positive(i)) lb.remove(i)
    }
    DenseVector(lb: _*)
  }

  /**
   * ベクトル * 行列を行う
   * DenseVector(1,2,3) * DenseMatrix((1,2,3),(4,5,6),(7,8,9)) はできないため
   * DenseVector(1,2,3) -> DenseMatrix((1,1,1),(2,2,2),(3,3,3))に変換する必要がある
   * R での V * Mに当たる
   * @param dv ベクトル
   * @param dm 行列
   * @return DenseMatrix
   */
  def multipleVector(dv: DenseVector[Double], dm: DenseMatrix[Double]) = {
    val arr = for {
      d <- dv.toArray
    } yield Stream.fill(dm.cols)(d)
    val dm2 = DenseMatrix(arr.toSeq: _*)
    dm :* dm2
  }
}

 

こんなことに/(^o^)\
なんでこんなに内部関数作ってんだよと罵ってください。

Rにはbreezeにない機能があり

|1 2 3|
|4 5 6|
|7 8 9|

という行列と

|true true false|

というベクトルから

|1 2|
|4 5|
|7 8|

という行列を生成できる関数が標準で備わっていたり、

ベクトル×行列の計算はRではできるけどbreezeではできない等の理由から、補助関数を作る必要がありました。
残念ながら関数multipleVectorの中の実装が良くないため、この自家製ginvは正則行列以外には対応しておりません。
とりあえず今回は正則行列だけ対応できれば問題ありませんでした。
今後改良する機会が来るだろうか…
というかもうちょっと汎用的に実装してbreezeにプルリクエスト送れって話ですね笑

 

まとめ

 

ScalaNLPのbreezeの一機能を紹介しました。
breezeは行列計算ライブラリの中でもかなり直感的でわかりやすい仕様になっていて、数学が得意でない人も気軽に使えるライブラリになっています。
何かのデータをバッチで計算するときにbreezeの行列を使うことで計算過程のわかりやすいコードが書け、後から見た人にやさしい保守性の高い実装ができるかもしれません。
しかしまだまだ発展途上のライブラリであるため、今回紹介したpinvのように簡易実装になっている機能やバグがいくつかありますので、各機能を使う前に十分に検証してみたほうがいいかと思います^ω^

それでは良いお年を。

]]>