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

  • 1,108 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,108
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
31
Comments
0
Likes
3

Embeds 0

No embeds

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