17705: Add dispatchcloud architecture page.
authorTom Clegg <tom@curii.com>
Wed, 9 Jun 2021 13:02:24 +0000 (09:02 -0400)
committerTom Clegg <tom@curii.com>
Wed, 9 Jun 2021 13:02:24 +0000 (09:02 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

doc/_config.yml
doc/architecture/dispatchcloud.html.textile.liquid [new file with mode: 0644]
doc/architecture/dispatchcloud.svg [new file with mode: 0644]

index 55987c062fad7666e4541477b60584788fc7027f..3b9d66a8542f7e492defcdb77f74b95d20ff47a5 100644 (file)
@@ -162,6 +162,7 @@ navbar:
       - architecture/manifest-format.html.textile.liquid
     - Computation with Crunch:
       - api/execution.html.textile.liquid
+      - architecture/dispatchcloud.html.textile.liquid
     - Other:
       - api/permission-model.html.textile.liquid
       - architecture/federation.html.textile.liquid
diff --git a/doc/architecture/dispatchcloud.html.textile.liquid b/doc/architecture/dispatchcloud.html.textile.liquid
new file mode 100644 (file)
index 0000000..cc7cb24
--- /dev/null
@@ -0,0 +1,99 @@
+---
+layout: default
+navsection: architecture
+title: Dispatching containers to cloud VMs
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+The arvados-dispatch-cloud component runs Arvados user containers on generic public cloud infrastructure by automatically creating and destroying VMs (“instances”) of various sizes according to demand, preparing the instances’ runtime environments, and running containers on them.
+
+This does not use a cloud provider’s container-execution service.
+
+h2. Overview
+
+In this diagram, the black edges show interactions involved in starting a VM instance and running a container. The blue edges show the “container shell” communication channel.
+
+!{max-width:40em}{{site.baseurl}}/architecture/dispatchcloud.svg!
+
+{% comment %}
+# svg generated using https://graphviz.it/
+digraph {
+    subgraph cluster_cloudvm {
+        node [color=black] [fillcolor=white] [style=filled];
+        style = filled;
+        color = lightgrey;
+        label = "cloud instance (VM)";
+        "SSH server" -> "crunch-run" [label = "start crunch-run"];
+        "crunch-run" -> docker [label = "create container"];
+        "crunch-run" -> docker [label = "shell"] [color = blue] [fontcolor = blue];
+        "crunch-run" -> container [label = "tcp/http"] [color = blue] [fontcolor = blue];
+        docker -> container;
+    }
+    "cloud provider" [shape=box] [style=dashed];
+    dispatcher -> controller [label = "get container queue"];
+    dispatcher -> "cloud provider" [label = "create/destroy/list VMs"];
+    "cloud provider" -> "SSH server" [label = "add authorized_keys"];
+    "crunch-run" -> controller [label = "update\ngateway ip:port,\ncontainer state,\noutput, ..."];
+    client -> controller [label = "shell/tcp/http (https tunnel)"] [color = blue] [fontcolor = blue];
+    controller -> "crunch-run" [label = "shell/tcp/http (https tunnel)"] [color = blue] [fontcolor = blue];
+    dispatcher -> "SSH server" [label = "start crunch-run"];
+}
+{% endcomment %}
+
+h2. Scheduling
+
+The dispatcher periodically polls arvados-controller to get a list of containers that are ready to run. Whenever this list changes, the dispatcher runs a scheduling loop that selects a suitable instance type for each container, allocates the highest priority containers to idle instances, requests new instances if needed, and shuts down instances that have been idle for longer than the configured idle timeout. Currently the dispatcher only runs one container at a time on an instance, even if the instance has enough RAM and CPUs to accommodate more.
+
+h2. Creating instances
+
+When creating a new instance, the dispatcher uses the cloud provider’s metadata feature to add a tag named “InstanceSetID”. This enables the dispatcher to recognize and reconnect to existing instances, and continue monitoring existing containers, after a restart or upgrade.
+
+When using the Azure cloud service, the dispatcher needs to first create a new network interface, then attach it to a new instance. The network interface is also tagged with “InstanceSetID”.
+
+If the cloud provider returns a rate-limiting error when creating a new instance, the dispatcher avoids requesting new instances for a short period, and shuts down idle nodes more aggressively (i.e., without waiting for the usual idle timeout to elapse) until a new instance is successfully created.
+
+h2. Recovering state after a restart
+
+Restarting the dispatcher does not interrupt containers that are already running. When the dispatcher starts up, it gets the cloud provider’s current list of instances that have the expected InstanceSetID tag value. It ignores instances without that tag, so it won’t interfere with other VM instances in the same cloud account. It runs the boot probe command on each instance, checks for containers that were started by a previous invocation and are still running, and resumes monitoring. Before dispatching any new containers to a previously existing instance, it ensures the crunch-run program is updated if needed.
+
+h2. Instance boot process
+
+When the cloud provider indicates that a new instance has been created, the dispatcher connects to the instance’s SSH service (see “instance control channel” below) and executes the configured boot probe command. If this fails, the dispatcher retries until the configured boot timeout is reached, then shuts down the instance. When the boot probe succeeds, the dispatcher copies the crunch-run program to the instance, and runs it to check for running containers before reporting the instance’s state as “idle” or “busy”. (Normally of course a freshly booted instance has no containers running, but this covers the case where the dispatcher itself has restarted and containers submitted by the previous dispatcher process are still running.)
+
+The dispatcher and crunch-run programs are both packaged in a single executable file: when dispatcher copies crunch-run to an instance, it is really copying itself. This ensures the dispatcher is always using the version of crunch-run that it expects.
+
+h2. Boot probe command
+
+The purpose of the boot probe command is to ensure the dispatcher does not try to schedule containers on an instance before the instance is ready, even if its SSH daemon comes up early in the boot process. The default boot probe command, @docker ps -q@, verifies that the docker daemon is running. It is also common to use a custom startup script in the VM image that writes a file when it finishes, and a boot probe command that checks for that file, such as @cat /var/run/boot.complete@.
+
+h2. Automatic instance shutdown
+
+Normally, the dispatcher shuts down any instance that has remained idle for 1 minute (see TimeoutIdle configuration) but there are some exceptions to this rule. If the cloud provider returns a quota error when trying to create a new instance, the dispatcher shuts down idle nodes right away, in case the idle nodes are contributing to the quota. Also, the operator can use the management API to set an instance’s idle behavior to “drain” or “hold”. “Drain” shuts down the instance as soon as it becomes idle, which can be used to recycle a suspect node without interrupting a running container. “Hold” keeps the instance alive indefinitely without scheduling additional containers on it, which can be used to investigate problems like a failed startup script.
+
+Each instance is tagged with its current idle behavior, which makes it visible in the cloud provider’s console and ensures the behavior is retained if dispatcher restarts.
+
+h2. Management API
+
+The dispatcher provides an HTTP management interface, which provides the operator with more visibility and control for purposes of troubleshooting and monitoring. APIs are provided to return details of current VM instances and running/scheduled containers as seen by the dispatcher, immediately terminate containers and instances, and control the on-idle behavior of instances. This interface also provides Prometheus metrics. APIs are documented on the "“Dispatching containers to cloud VMs” wiki":https://dev.arvados.org/projects/arvados/wiki/Dispatching_containers_to_cloud_VMs#Management-API.
+
+h2. Instance control channel (SSH)
+
+The dispatcher uses a multiplexed SSH connection to monitor instance boot progress, install the crunch-run supervisor program, start and stop containers, and detect crashed containers and failing instances. It establishes a persistent SSH connection to each cloud instance when the instance first appears, retrying/reconnecting as needed.
+
+Cloud VMs typically generate a random SSH host key at boot time, making host key verification impossible. To provide some assurance the dispatcher is connecting to the intended instance, when it creates a new instance the dispatcher generates a random “instance secret”, uses the cloud provider’s bootstrap command feature to save it in @/var/run/arvados-instance-secret@ on the new instance, and executes @cat /var/run/arvados-instance-secret@ to verify the instance’ identity when first connecting to its SSH server. Each instance is also tagged with its instance secret, so it can still be verified after a dispatcher restart.
+
+h2. Container communication channel (https tunnel)
+
+The crunch-run program runs a gateway server which facilitates the “container shell” feature without sending traffic through the dispatcher process. The gateway server accepts TLS connections from arvados-controller on a dynamic TCP port (typically in the range 32768-60999, see @sysctl net.ipv4.ip_local_port_range@). Crunch-run saves the selected port, along with the external IP address of the VM instance as seen by the dispatcher, in the @gateway_address@ field in the container record so arvados-controller can connect to it.
+
+On the client host (typically a shell node or a user’s workstation) the @arvados-client shell@ command sends an https “connect” request to arvados-controller, which sends an https “connect” request to the gateway server. These tunnels convey SSH protocol traffic between the user’s SSH client and crunch-run’s built-in SSH server, which uses @docker exec@ to run commands inside the container.
+
+Arvados-controller and crunch-run gateway server authenticate each other using a self-signed certificate and a shared secret based on the cluster-wide @SystemRootToken@. If that token changes (and the dispatcher restarts to load the new token) while a container is running, the container will stop accepting container shell traffic.
+
+h2. Scaling
+
+Architecturally, the dispatcher is _designed_ to accommodate multiple concurrent dispatcher processes on multiple hosts, each using a different authorization token, but such a configuration is not yet supported. Currently, each cluster should run a single dispatcher process. A single process can support thousands of concurrent VM instances.
diff --git a/doc/architecture/dispatchcloud.svg b/doc/architecture/dispatchcloud.svg
new file mode 100644 (file)
index 0000000..d658c61
--- /dev/null
@@ -0,0 +1 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="542.87pt" height="561.4pt" viewBox="0 0 542.87 561.4"><style type="text/css">.dashed {stroke-dasharray: 5,5} .dotted {stroke-dasharray: 1,5} .overlay {fill: none; pointer-events: all}</style><g><g transform="translate(4, 557.4000244140625) scale(1,1)"><polygon stroke="#fffffe" stroke-opacity="0" fill="#ffffff" points="-4,4 -4,-557.4 538.87,-557.4 538.87,4"></polygon><g class="subgraph"><title>cluster_cloudvm</title><path stroke="#d3d3d3" fill="#d3d3d3" d="M 30.22,-8 L 30.22,-385.8,209.22,-385.8,209.22,-8 Z"></path><text x="119.72" y="-369.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">cloud instance (VM)</text></g><g class="node"><title>SSH server</title><path stroke="#000000" fill="#ffffff" d="M 93.22,-335 m -54.99,0 a 54.99,18 0 1,0 109.98,0 a 54.99,18 0 1,0 -109.98,0"></path><text x="93.22" y="-330.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">SSH server</text></g><g class="node"><title>crunch-run</title><path stroke="#000000" fill="#ffffff" d="M 148.22,-195.8 m -53.29,0 a 53.29,18 0 1,0 106.58,0 a 53.29,18 0 1,0 -106.58,0"></path><text x="148.22" y="-191.6" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">crunch-run</text></g><g class="relation" style="opacity: 1;"><title>SSH server-&gt;crunch-run</title><path stroke="#000000" fill="none" d="M 82.52,-317.19 C 79.55,-311.62,76.72,-305.24,75.2,-299,68.17,-269.97,59.29,-257.07,75.2,-231.8,80.6,-223.24,88.73,-216.72,97.64,-211.78"></path><path class="solid" stroke="#000000" fill="#000000" d="M 99.28,-214.87 L 106.73,-207.34,96.21,-208.58 Z"></path><text x="119.72" y="-261.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">start crunch-run</text></g><g class="node"><title>docker</title><path stroke="#000000" fill="#ffffff" d="M 85.22,-107 m -37.12,0 a 37.12,18 0 1,0 74.24,0 a 37.12,18 0 1,0 -74.24,0"></path><text x="85.22" y="-102.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">docker</text></g><g class="relation" style="opacity: 1;"><title>crunch-run-&gt;docker</title><path stroke="#000000" fill="none" d="M 100.13,-187.64 C 83.75,-182.64,67.09,-174.17,57.22,-159.8,51.11,-150.9,54.81,-140.52,61.26,-131.4"></path><path class="solid" stroke="#000000" fill="#000000" d="M 64.14,-133.41 L 67.76,-123.46,58.72,-128.98 Z"></path><path stroke="#0000ff" fill="none" d="M 151.54,-177.75 C 152.71,-167.03,152.46,-153.32,146.22,-143,141.05,-134.47,132.98,-127.82,124.38,-122.72"></path><path class="solid" stroke="#0000ff" fill="#0000ff" d="M 125.8,-119.52 L 115.32,-117.96,122.54,-125.71 Z"></path><text x="102.71" y="-147.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">create container</text><text x="165.44" y="-147.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#0000ff">shell</text></g><g class="node"><title>container</title><path stroke="#000000" fill="#ffffff" d="M 119.22,-34 m -46.93,0 a 46.93,18 0 1,0 93.86,0 a 46.93,18 0 1,0 -93.86,0"></path><text x="119.22" y="-29.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">container</text></g><g class="relation" style="opacity: 1;"><title>crunch-run-&gt;container</title><path stroke="#0000ff" fill="none" d="M 168.59,-179 C 174.07,-173.57,179.29,-167.02,182.22,-159.8,185.02,-152.88,184.16,-150.21,182.22,-143,173.79,-111.83,153.88,-80.42,138.69,-59.57"></path><path class="solid" stroke="#0000ff" fill="#0000ff" d="M 141.3,-57.22 L 132.51,-51.32,135.7,-61.42 Z"></path><text x="197.6" y="-102.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#0000ff">tcp/http</text></g><g class="relation" style="opacity: 1;"><title>docker-&gt;container</title><path stroke="#000000" fill="none" d="M 93.27,-89.17 C 97.24,-80.9,102.11,-70.72,106.56,-61.44"></path><path class="solid" stroke="#000000" fill="#000000" d="M 109.82,-62.73 L 110.98,-52.2,103.5,-59.71 Z"></path></g><g class="node"><title>controller</title><path stroke="#000000" fill="none" d="M 292.22,-335 m -48.65,0 a 48.65,18 0 1,0 97.3,0 a 48.65,18 0 1,0 -97.3,0"></path><text x="292.22" y="-330.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">controller</text></g><g class="relation" style="opacity: 1;"><title>crunch-run-&gt;controller</title><path stroke="#000000" fill="none" d="M 156.37,-213.74 C 159,-219.43,161.84,-225.84,164.22,-231.8,175.91,-261.13,164.83,-276.77,187.24,-299,200.71,-312.35,219.4,-320.61,237.28,-325.72"></path><path class="solid" stroke="#000000" fill="#000000" d="M 236.54,-329.14 L 247.09,-328.25,238.28,-322.37 Z"></path><text x="233.7" y="-286.4" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">update</text><text x="233.7" y="-269.6" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">gateway ip:port,</text><text x="233.7" y="-252.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">container state,</text><text x="233.7" y="-236" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">output, ...</text></g><g class="node"><title>cloud provider</title><path class="dashed" stroke="#000000" fill="none" d="M 202.25,-464.6 L 104.18,-464.6,104.18,-428.6,202.25,-428.6 Z"></path><text x="153.22" y="-442.4" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">cloud provider</text></g><g class="relation" style="opacity: 1;"><title>cloud provider-&gt;SSH server</title><path stroke="#000000" fill="none" d="M 143.84,-428.47 C 134.07,-410.62,118.63,-382.42,107.37,-361.85"></path><path class="solid" stroke="#000000" fill="#000000" d="M 110.4,-360.1 L 102.52,-353,104.26,-363.46 Z"></path><text x="191.95" y="-398" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">add authorized_keys</text></g><g class="node"><title>dispatcher</title><path stroke="#000000" fill="none" d="M 121.22,-535.4 m -50.94,0 a 50.94,18 0 1,0 101.88,0 a 50.94,18 0 1,0 -101.88,0"></path><text x="121.22" y="-531.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">dispatcher</text></g><g class="relation" style="opacity: 1;"><title>dispatcher-&gt;SSH server</title><path stroke="#000000" fill="none" d="M 84.19,-522.95 C 57.27,-512.46,22.76,-494.01,6.2,-464.6,-15.98,-425.19,28.29,-382.2,61.44,-357.25"></path><path class="solid" stroke="#000000" fill="#000000" d="M 63.56,-360.03 L 69.58,-351.31,59.43,-354.38 Z"></path><text x="50.72" y="-442.4" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">start crunch-run</text></g><g class="relation" style="opacity: 1;"><title>dispatcher-&gt;cloud provider</title><path stroke="#000000" fill="none" d="M 118.61,-517.02 C 117.76,-506.68,117.95,-493.49,122.2,-482.6,123.51,-479.25,125.3,-476,127.36,-472.94"></path><path class="solid" stroke="#000000" fill="#000000" d="M 130.31,-474.84 L 133.71,-464.8,124.8,-470.53 Z"></path><text x="187.72" y="-486.8" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">create/destroy/list VMs</text></g><g class="relation" style="opacity: 1;"><title>dispatcher-&gt;controller</title><path stroke="#000000" fill="none" d="M 171.65,-533.15 C 199.28,-529.81,232.11,-521.04,253.22,-499.4,262.74,-489.64,279.17,-406.88,287.33,-363.03"></path><path class="solid" stroke="#000000" fill="#000000" d="M 290.8,-363.51 L 289.17,-353.04,283.91,-362.24 Z"></path><text x="330.03" y="-442.4" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">get container queue</text></g><g class="relation" style="opacity: 1;"><title>controller-&gt;crunch-run</title><path stroke="#0000ff" fill="none" d="M 295.12,-316.84 C 297.86,-294.44,298.93,-255.32,278.22,-231.8,261.45,-212.76,235.53,-203.57,211.23,-199.31"></path><path class="solid" stroke="#0000ff" fill="#0000ff" d="M 211.72,-195.84 L 201.31,-197.81,210.68,-202.76 Z"></path><text x="372.04" y="-261.2" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#0000ff">shell/tcp/http (https tunnel)</text></g><g class="node"><title>client</title><path stroke="#000000" fill="none" d="M 427.22,-446.6 m -32.48,0 a 32.48,18 0 1,0 64.96,0 a 32.48,18 0 1,0 -64.96,0"></path><text x="427.22" y="-442.4" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#000000">client</text></g><g class="relation" style="opacity: 1;"><title>client-&gt;controller</title><path stroke="#0000ff" fill="none" d="M 409.57,-431.27 C 386.64,-412.66,346.37,-379.96,319.49,-358.15"></path><path class="solid" stroke="#0000ff" fill="#0000ff" d="M 321.59,-355.34 L 311.62,-351.75,317.17,-360.77 Z"></path><text x="459.04" y="-398" text-anchor="middle" font-family="'Times-Roman',serif" font-size="14" fill="#0000ff">shell/tcp/http (https tunnel)</text></g></g></g></svg>
\ No newline at end of file