マスタリング DEA/NG 第2版
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

マスタリング DEA/NG 第2版

  • 1,429 views
Uploaded on

Cloud Foundryのコンポーネントの1つであるDEAの次期バージョンについて、ソースコードを追いながら簡単な解説を行います。

Cloud Foundryのコンポーネントの1つであるDEAの次期バージョンについて、ソースコードを追いながら簡単な解説を行います。

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,429
On Slideshare
1,421
From Embeds
8
Number of Embeds
1

Actions

Shares
Downloads
27
Comments
0
Likes
3

Embeds 8

https://twitter.com 8

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. マスタリング DEA/NG 第2版岩嵜 雄大NTT Software Innovation Center2012-09-13 NTT Software Innovation Center
  • 2. はじめに  情報はすべて資料作成当時のものです – 更新が頻繁なため情報がすぐに古くなります – 必ず最新のソースコードを確認してください2012-09-13 NTT Software Innovation Center 2
  • 3. 今日の内容  そもそもDEAとは – 簡単な紹介  DEA_NG起動から、Warden上でのアプリ ケーション実行までを追う – エントリーポイントからインスタンス起動まで  Promiseパターンについて – DEA_NGに頻出するPromiseとは何者か2012-09-13 NTT Software Innovation Center 3
  • 4. そもそもDEAとは2012-09-13 NTT Software Innovation Center 4
  • 5. DEAとは  ユーザのアプリケーションを実行するためのコン ポーネント – Dropletと呼ばれる形式のアプリケーションを実行する – DEAを増やすことでCloud Foundryの実行能力はスケー ルする App App App App App App App App App App App DEA App App DEA App App App DEA DEA2012-09-13 NTT Software Innovation Center 5
  • 6. DEAがやること  Dropletのダウンロード  ランタイム(Java, Ruby, etc)の管理  アプリケーション隔離用コンテナの管理 – コンテナには独自の「Warden」を使用  アプリケーションの管理・死活監視2012-09-13 NTT Software Innovation Center 6
  • 7. DEAサーバの起動から アプリケーションの起動まで2012-09-13 NTT Software Innovation Center 7
  • 8. 全体的な概念図(今日話す範囲) DEA Instance Instance Instance Instance 1対1対応で生成や起動などを担当 通信はWarden Protocol 生成CCからの命令 NATS基本的にはイベントドリブン App App App App App DEAプロセスの セットアップ ContainerContainerContainer ContainerContainer bin/dea BootStrap Warden VM ※他にもDroplet RegistryやDirectory Serverなどがサブモジュールがある2012-09-13 NTT Software Innovation Center 8
  • 9. エントリーポイント  /bin/dea bootstrap = Dea::Bootstrap.new(config) EM.run do bootstrap.setup bootstrap.start end – コマンドライン引数のパース – DEAプロセスをキック2012-09-13 NTT Software Innovation Center 9
  • 10. DEA serverの起動  /lib/dea/bootstrap.rb – プロセスの初期セットアップ (setup_*) – NATSクライアントの起動 • あとはNATSまかせ def start start_component start_nats start_directory_server start_finish end … def setup_nats @nats = Dea::Nats.new(self, config) end2012-09-13 NTT Software Innovation Center 10
  • 11. NATSに対するディスパッチャーの登録  /lib/dea/nats.rb – アプリケーションインスタンスの起動イベント def start … subscribe("dea.#{bootstrap.uuid}.start") do |message| bootstrap.handle_dea_directed_start(message) end … end def subscribe(subject) sid = client.subscribe(subject) do |raw_data, respond_to| message = Message.decode(self, subject, raw_data, respond_to) logger.debug "Received on #{subject.inspect}: #{message.data.inspect}" yield message end @sids[subject] = sid end2012-09-13 NTT Software Innovation Center 11
  • 12. Bootstrap側のハンドラ  DEA::Instance オブジェクトを生成 – オブジェクトはレジストリに登録する def handle_dea_directed_start(message) instance = create_instance(message.data) … instance.start end … def create_instance(attributes) instance = Instance.new(self, Instance.translate_attributes(attributes)) instance.on(Instance::Transition.new(:born, :starting)) do instance_registry.register(instance) end … (他のインスタンス状態変化トリガ) instance end2012-09-13 NTT Software Innovation Center 12
  • 13. インスタンスの起動  /lib/dea/instance.rb – Promiseについては後述 def start(&callback) … if !droplet.droplet_exist? logger.info("Starting droplet download") … promise_droplet_download.resolve ドロップレット end をダウンロード promise_container = Promise.new do |p| promise_create_container.resolve promise_setup_network.resolve promise_limit_disk.resolve promise_limit_memory.resolve コンテナの準備 p.deliver end … promise_extract_droplet.resolve promise_prepare_start_script.resolve インスタンスの … promise_start.resolve 起動 end2012-09-13 NTT Software Innovation Center 13
  • 14. コンテナの生成設定  DEAホストのDropletとランタイムをコンテナ内 から見えるように追加のマウント情報を設定def promise_create_container… connection = promise_warden_connection(:app).resolve # Droplet and runtime bind_mounts = [droplet.droplet_dirname, runtime.dirname].map do |path| bind_mount = ::Warden::Protocol::CreateRequest::BindMount.new bind_mount.src_path = path bind_mount.dst_path = path bind_mount.mode = ::Warden::Protocol::CreateRequest::BindMount::Mode::RO bind_mount end…次ページに続く2012-09-13 NTT Software Innovation Center 14
  • 15. コンテナの生成設定  ライブラリ類もマウントとして追加 …前ページから # Extra mounts (these typically include libs like pq, mysql, etc) bootstrap.config["bind_mounts"].each do |bm| bind_mount = ::Warden::Protocol::CreateRequest::BindMount.new bind_mount.src_path = bm["src_path"] bind_mount.dst_path = bm["dst_path"] || bm["src_path"] mode = bm["mode"] || "ro" bind_mount.mode = BIND_MOUNT_MODE_MAP[mode] bind_mounts << bind_mount end …次ページへ2012-09-13 NTT Software Innovation Center 15
  • 16. アプリケーションインスタンスの実行  Warden Protocolによりコンテナを起動 …前ページから create_request = ::Warden::Protocol::CreateRequest.new create_request.bind_mounts = bind_mounts response =promise_warden_call (connection, create_request).resolve … end end  コンテナ起動後にメモリとディスクの制限を行う – promise_setup_network – promise_limit_disk – promise_limit_memory2012-09-13 NTT Software Innovation Center 16
  • 17. 【寄り道】 promise_warden_callで何が起きているのか  connectionは EM::Warden::Client::Connection def promise_warden_call(connection, request) Promise.new do |p| logger.debug2(request.inspect) connection.call(request) do |result| logger.debug2(result.inspect) … if error logger.warn "Request failed: #{request.inspect}" logger.log_exception(error) p.fail(error) else p.deliver(response) end end … end2012-09-13 NTT Software Innovation Center 17
  • 18. 【寄り道】 Warden Clientの話  Warden本体については「すごく分かるWarden」 – http://www.slideshare.net/i_yudai/warden  3つのライブラリ – warden-client • Unix Socketによる(簡易な)実装 • ライブラリ的なものも含む – em-warden-client • EventMachineを使用した実装 • warden-clientに依存 – warden-protocol • サーバクライアント間の通信に使うクラスのセット2012-09-13 NTT Software Innovation Center 18
  • 19. 【寄り道】 Warden Protocol  Wardenに対する各種命令 • ping • run • create • info • stop • LimitDisk • spawn • LimitMemory • link • net_in • stream • net_out  コマンドラインコマンドとほぼ同等ping - ping wardencreate [OPTION OPTION ...] - create container, optionally pass options.destroy <handle> - shutdown container <handle>stop <handle> - stop all processes in <handle>spawn <handle> cmd - spawns cmd inside container <handle>, returns #jobidlink <handle> #jobid - do blocking read on results from #jobidstream <handle> #jobid - do blocking stream on results from #jobidrun <handle> cmd - short hand for stream(spawn(cmd)) i.e. spawns cmd, streams the resultlist - list containersinfo <handle> - show metadata for container <handle>limit <handle> mem [<value>] - set or get the memory limit for the container (in bytes)net <handle> #in - forward port #in on external interface to container <handle>net <handle> #out <address[/mask][:port]> - allow traffic from the container <handle> to address<address>2012-09-13 NTT Software Innovation Center 19
  • 20. 【寄り道】 EM::Warden::FiberAwareClient  /em-warden-client/lib/em/warden/client.rb – create()メソッドは存在しない • method_missing()からcall()が呼ばれる def call(*args, &blk) … f = Fiber.current @connection.call(*args) {|res| f.resume(res) } result = Fiber.yield result.get EventMachine::Warden::Client::Connection end def method_missing(method, *args, &blk) call(method, *args, &blk) end2012-09-13 NTT Software Innovation Center 20
  • 21. 【寄り道】 EM::Warden::FiberAwareClient  /em-warden-client/lib/em/warden/client.rb – create()メソッドは存在しない • method_missing()からcall()が呼ばれる def call(*args, &blk) … 使わなくなりました f = Fiber.current @connection.call(*args) {|res| f.resume(res) } result = Fiber.yield result.get EventMachine::Warden::Client::Connection end def method_missing(method, *args, &blk) call(method, *args, &blk) end2012-09-13 NTT Software Innovation Center 21
  • 22. 【寄り道】 EventMachine::Warden::Client::Connection  /em-warden- client/lib/em/warden/client/connection.rb – EM::Connectionを継承def call(*args, &blk) if args.first.kind_of?(::Warden::Protocol::BaseRequest) request = args.first Protocolオブジェクトが else 渡されている場合 … request = ::Warden::Client::V1.request_from_v1(args.dup) @v1mode = true argsに”create”が渡されていれば end Warden::Protocol::CreateRequest @request_queue << { :request => request, :callback => blk } process_queueend2012-09-13 NTT Software Innovation Center 22
  • 23. 【寄り道】 コンテナ生成時におけるマウントの追加  /warden/warden/lib/warden/container/linux.rb – フックにマウントの命令を追加する def write_bind_mount_commands(request) return if request.bind_mounts.nil? || request.bind_mounts.empty? File.open(File.join(container_path, "hook-parent-before-clone.sh"), "a") do |file| … request.bind_mounts.each do |bind_mount| src_path = bind_mount.src_path dst_path = bind_mount.dst_path # Fix up destination path to be an absolute path inside the union dst_path = File.join(container_path, "union", dst_path[1..-1]) mode = case bind_mount.mode when Protocol::CreateRequest::BindMount::Mode::RO "ro" when Protocol::CreateRequest::BindMount::Mode::RW "rw" else raise "Unknown mode" end file.puts "mkdir -p #{dst_path}" % [dst_path] file.puts "mount -n --bind #{src_path} #{dst_path}" file.puts "mount -n --bind -o remount,#{mode} #{src_path} #{dst_path}" end end end2012-09-13 NTT Software Innovation Center 23
  • 24. Dropletの展開  コンテナ上でtarコマンドを実行 def promise_extract_droplet Promise.new do |p| connection = promise_warden_connection(:app).resolve script = "tar zxf #{droplet.droplet_path}" promise_warden_run(connection, script).resolve p.deliver end end2012-09-13 NTT Software Innovation Center 24
  • 25. スタートアップスクリプトの調整  アプリケーション起動スクリプトのプレースホル ダを置換する – @がデリミタ def promise_prepare_start_script Promise.new do |p| connection = promise_warden_connection(:app).resolve script = "sed -i s@%VCAP_LOCAL_RUNTIME%@#{runtime.executable}@g startup" promise_warden_run(connection, script).resolve p.deliver end end2012-09-13 NTT Software Innovation Center 25
  • 26. スタートアップスクリプトの呼び出し def promise_start Promise.new do |p| script = [] script << "renice 0 $$" script << "ulimit -n %d" % self.file_descriptor_limit script << "ulimit -u %d" % 512 script << "umask 077" env = Env.new(self) env.env.each do |(key, value)| script << "export %s=%s" % [key, value] end startup = "./startup" # Pass port to `startup` if we have one if self.instance_host_port startup << " -p %d" % self.instance_host_port end script << startup script << "exit" connection = promise_warden_connection(:app).resolve request = ::Warden::Protocol::SpawnRequest.new request.handle = attributes["warden_handle"] request.script = script.join("¥n") response = promise_warden_call(connection, request).resolve attributes["warden_job_id"] = response.job_id p.deliver end end2012-09-13 NTT Software Innovation Center 26
  • 27. Promise パターンについて2012-09-13 NTT Software Innovation Center 27
  • 28. Promise(future)パターンとは DEA::InstanceがPromiseまみれになった • 非同期プログラミング – Wardenとの通信が発生する • ブロックを避けたい – Wardenの処理待ち中ブロックされたくない • deferredに似てる – JavaScriptプログラマーにはおなじみ2012-09-13 NTT Software Innovation Center 28
  • 29. イメージ 処理AのIO待ち中に処理Bを済ませておきたい • 処理Aをスタートし、引換券(Promise)をもらう • IO待ち中に処理Bを開始する • どうしても処理Aの結果がほしくなったら引換券を 実際の値に交換してもらう(resolve) • 処理Aは結果が出たら値を記録しておく (deliver)2012-09-13 NTT Software Innovation Center 29