{"action":"created","issue":{"url":"https://api.github.com/repos/docker/docker/issues/13884","labels_url":"https://api.github.com/repos/docker/docker/issues/13884/labels{/name}","comments_url":"https://api.github.com/repos/docker/docker/issues/13884/comments","events_url":"https://api.github.com/repos/docker/docker/issues/13884/events","html_url":"https://github.com/docker/docker/pull/13884","id":87324654,"number":13884,"title":"Proposal: docker daemon's SPoF and hot upgrade issue","user":{"login":"mudverma","id":4323340,"avatar_url":"https://avatars.githubusercontent.com/u/4323340?v=3","gravatar_id":"","url":"https://api.github.com/users/mudverma","html_url":"https://github.com/mudverma","followers_url":"https://api.github.com/users/mudverma/followers","following_url":"https://api.github.com/users/mudverma/following{/other_user}","gists_url":"https://api.github.com/users/mudverma/gists{/gist_id}","starred_url":"https://api.github.com/users/mudverma/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mudverma/subscriptions","organizations_url":"https://api.github.com/users/mudverma/orgs","repos_url":"https://api.github.com/users/mudverma/repos","events_url":"https://api.github.com/users/mudverma/events{/privacy}","received_events_url":"https://api.github.com/users/mudverma/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/docker/docker/labels/dco/no","name":"dco/no","color":"E6781E"},{"url":"https://api.github.com/repos/docker/docker/labels/status/1-needs-design-review","name":"status/1-needs-design-review","color":"fef2c0"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":18,"created_at":"2015-06-11T12:11:33Z","updated_at":"2015-06-29T05:19:53Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/docker/docker/pulls/13884","html_url":"https://github.com/docker/docker/pull/13884","diff_url":"https://github.com/docker/docker/pull/13884.diff","patch_url":"https://github.com/docker/docker/pull/13884.patch"},"body":"**Background**:\r\n===============================\r\nAs per the current architecture of Docker, all containers are spawned as children of docker daemon. This parent-child relationship between the docker daemon and containers provides a straight forward way of signalling/communication between them. However, this tight coupling between the containers and daemon results in some issues which are critical for containers' up-time, stability and high availability. Few of the issues with this approach are:\r\n\r\n1. Daemon's death (restart, kill, abrupt crash in daemon's code) causes all the running containers to die as well.\r\n2. An upgrade to daemon can not be performed without impacting the up-time/stability of containers. \r\n\r\nBoth of these issues become even more important in production environments such as containers cloud where different containers running on a server might belong to same or different clients and might host highly available or stateless services. In these scenarios, a container downtime caused by external factors such as daemon's death/upgrade, is highly undesirable.  \r\n\r\nThe 2nd issue was opened by @shykes  in 2013, but this is still an open item https://github.com/docker/docker/issues/2658 \r\n\r\n\r\n**Goals:**\r\n========================\r\n1. Containers should run independently of docker daemon and should continue to function normally even if docker daemon dies/restarts\r\n2. When containers die/exit their exit status should be appropriately communicated to docker daemon (whenever it restarts)\r\n3. All commands that are used to interact with containers (start/stop/kill/exec/pause, etc) and I/O (stderr/stdout) redirection should work normally\r\n\r\n**Findings:**\r\n=============================\r\nBased on our investigation and experimentation with docker we found that once started, a container can function stand-alone and does not require daemon's presence for the execution of an encapsulated service. \r\n\r\nWe changed the daemon's code such that upon it's death, containers would become orphaned and shall be adopted by INIT.  We ran the official mysql image. We were able to connect to, and use the mysql service even when the daemon was not running and container had become orphaned. \r\n\r\nThe namespaces (pid,network, ipc, mnt, uts) and cgroups etc, which are the building blocks for container creation and execution, continue to exist and function normally as they are provided by the linux kernel. Therefore, there does not seem to be any reason for a container to stop functioning when the daemon is not present.\r\n\r\n**Proposal:**\r\n====================\r\nIn context of our findings and our goals, we propose two alternative design models through which containers will no longer be tightly coupled with the daemon either always (*proposal A*) or after daemon's death (*proposal B*). Both of these models require an external communication/signalling mechanism between the daemon and the containers. \r\n \r\n* **Proposal A**:  Docker daemon starts the containers in such a way that they are the children of INIT from the very beginning and are decoupled from the daemon.  All the running containers and daemon shall be siblings in process tree and will not be impacted by daemon's absence.  \r\n\r\n* **Proposal B**:  A hybrid model  where newly spawned containers shall be the children of docker daemon as in the current design. However, upon abrupt daemon failure or upgrade, all the running containers shall become orphaned and will move to INIT.  When the daemon restarts, it will detect that there are old running containers (current code detects and kills such containers) and would manage them by external communication means (new feature to be added). Consequently, all the containers that are spawned by the current instance of daemon will be its decedents and other containers that were started by previous daemon instances will be independent processes and would require external mode of communication. \r\n\r\n**Communication between containers and the daemon:**\r\n-----------------------\r\nBoth of these proposals require some sort of two-way communication mechanism between the daemon and the containers. For example, how would a daemon get notified when a container finishes its execution? Also, how would the daemon pass the commands to containers. In current design, daemon does \"wait\" on child process (container) in a go routine. This can be tackled by having a dedicated parent monitor process for each container, whose job will be to wait on the container and communicate with the daemon.  Below describes the communication: \r\n\r\n* **From a container to daemon**: Once the container terminates, waiting monitor parent process can communicate the exit status to the docker daemon either by connecting through sockets or by sending special signals to the daemon process (SIGRTMIN-SIGRTMAX, SIGUSR1,SIGUSR2) and writing to a special file from where daemon can read the status upon receiving a special signal from the container. \r\n* **From daemon to a container**: Similarly, daemon can be made aware of container's pid by monitor process so that it can directly interact with the containers using signals (SIGKILL,SIGTERM,SIGSTP, SIGCONT etc).\r\n \r\n* **For I/O**: Interprocess pipes can be used to redirect stdout, stderr etc. \r\n\r\n \r\n**Implementation:**\r\n=======================================\r\n**Proposal A:**\r\n\r\n* Daemon first spawns a monitor process and that monitor process spawns the container. Once the container is fully spawned, monitor process replaces itself with an *ultra lightweight code* (unix exec) whose job is to wait on its child process (container). \r\n\r\n* Daemon then daemonise the monitor process so that *monitor+container* become orphan and are adopted by INIT. \r\n\r\n* Daemon interacts with the monitor process using sockets or send direct signals to container process (monitor can communicate the container pid to daemon) for management (stop, start, pause, kill, etc).\r\n\r\n* Once the container finishes its execution, its waiting monitor process can communicate the exit status to the daemon. There are two possibilities here \r\n    * Daemon is alive:- communication is straight forward and the monitoring process can exit after supplying the status to the daemon \r\n    * Daemon is dead:- Monitoring process will wait until the daemon is up, communicate the status and exit. Alternatively it can write to a file, which the new instance of daemon when it would come back up, will read from. \r\n![screen shot 2015-06-11 at 3 46 06 pm](https://cloud.githubusercontent.com/assets/4323340/8104805/5c528a7e-1051-11e5-9ba3-e33b988f5f5c.png)\r\n\r\n**Proposal B:**\r\n\r\n*  Monitor+container move to INIT only after the daemon dies (becomes orphaned). This would require a change in cleanup code. \r\n\r\n* New instance of daemon becomes aware of old running containers and communicates with them using the techniques described in proposal A. \r\n\r\n* Monitor process can look for its ppid (parent pid) and if it is 1, it can assume that it is orphaned and must communicate with daemon by other means. If not, it would have daemon as it's parent and communication flow be similar to what we have in current design. \r\n\r\n![screen shot 2015-06-11 at 3 46 45 pm](https://cloud.githubusercontent.com/assets/4323340/8104987/abddc2d8-1052-11e5-98b1-956268d70ee0.png)\r\n\r\n\r\n**Proposal A vs Proposal B:**\r\n\r\n* *Proposal A* requires daemonising the monitor just after it is spawned. (Daemonising a child process is not inherently supported by golang which docker uses but can be worked around by using double fork).\r\nhttps://github.com/golang/go/issues/227\r\n\r\n* *Proposal B* does not require any such functionality, and upon daemon's death, all the child monitors (orphaned now) are moved to INIT along with the containers. However,  \r\n    * Proposal B is easier to code although slightly less cleaner from a design perspective as it would require separate maintenance (different codes paths/checks) for the container that daemon owns (descendants) and the containers that it manages (siblings)\r\n\r\n\r\n**Use Cases:**\r\n=============================\r\n* Containers' high availability\r\n* Hot upgrades\r\n* Scalability\r\n    * Scalability is possible because a particular daemon will not be the owner of containers. Therefore, multiple daemons can be allowed to run on same host doing load balancing of requests coming from different channels (CLI, remote API etc).\r\n    * However, we don't know if docker daemon is a performance bottleneck (most likely not). Daemon seems just to be a control channel/management point and does not picture in data/critical path. \r\n    * Nonetheless, one can have a redundant docker daemon sharing the same DB, so that even upon upgrades or failures, daemon continue to serve requests - **Daemon's high availability**\r\n\r\n\r\nCommands' analysis:\r\n===============================\r\nFollowing table lists all the commands that will be (not) impacted and would require a code change. \r\n\r\n|Docker command |\tDescription  |\tImpact |\r\n-------------------------|------------------|-------------\r\nexec\t | run a new command in a running container\t| impacted, doable (using setns)\r\nAttach | attach to a running container |\tImpacted, doable (using pipes or by connecting to stdout of the container by other means)\r\nbuild\t | build an image from docker file | Not impacted, unrelated\r\ncommit  |\tcreate a new images from a container's changes | Not impacted, related (read file system)\r\ncp |\tcopy files from a location to another  |\tnot impacted, related (read file system)\r\ndiff |\tinspect changes on a container's file system\t| not impacted, related (read file system)\r\nevents  |\tglobal daemon events\t| not impacted, unrelated\r\nexport/save |\tcreate a  tar archive | \tnot impacted, unrelated\r\nhistory |\thistory of an image\t| not impacted, unrelated\r\nimages |\tlist images  |\tnot impacted, unrelated\r\nimport/load | tar to image\t | not impacted, unrelated\r\ninfo\t| daemon version build etc\t| not impacted, unrelated\r\ninspect\t| return container's json\t| not impacted, related (read json from file system)\r\nkill\t| kill the running container\t| impacted, doable (need to pass the signal)\r\nlogs\t| fetch the logs of a container\t| impacted, doable (reading from a special filefd/pipe)\r\nport\t| shows public facing port-> NATed to private port\t| not impacted, related\r\npause | Pause all running processes in a container   |\timpacted, doable (need to send SIGSTOP)\r\nstart/unpause\t| start all stopped processes in a container |\timpacted, doable (need to send SIGCONT)\r\nps | list all containers  |\tnot impacted, unrelated\r\npull/push\t| pull and push images from/to repo |\tnot impacted, unrelated\r\nrestart |\trestart a running container\t| impacted, doable (kill the old container, start afresh)\r\nrm\t| remove the container\t| impacted, doable\r\nrmi\t| remove the image\t| not impacted, unrelated\r\nrun\t| run a command in a new container | impacted, doable (start a new container)\r\nsearch |\tsearch repo |\tnot impacted, unrelated\r\nstop\t| stop a running container  |\timpacted, doable (need to send SIGTERM/KILL)\r\ntag\t| tag an image\t| not impacted, unrelated\r\ntop\t| lookup the running processes of a container\t | impacted, doable (using setns + top) \r\nversion\t| docker version info |\tnot impacted, unrelated\r\nwait\t| block until a container stops and print exit code\t| impacted, doable using our external communication mode\r\n\r\n\r\nLimitations:\r\n===============================\r\nWe will have the overhead of extra monitor processes (as many as the number of running containers). \r\n\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/docker/docker/issues/comments/116438180","html_url":"https://github.com/docker/docker/pull/13884#issuecomment-116438180","issue_url":"https://api.github.com/repos/docker/docker/issues/13884","id":116438180,"user":{"login":"nadgowdas","id":8685412,"avatar_url":"https://avatars.githubusercontent.com/u/8685412?v=3","gravatar_id":"","url":"https://api.github.com/users/nadgowdas","html_url":"https://github.com/nadgowdas","followers_url":"https://api.github.com/users/nadgowdas/followers","following_url":"https://api.github.com/users/nadgowdas/following{/other_user}","gists_url":"https://api.github.com/users/nadgowdas/gists{/gist_id}","starred_url":"https://api.github.com/users/nadgowdas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nadgowdas/subscriptions","organizations_url":"https://api.github.com/users/nadgowdas/orgs","repos_url":"https://api.github.com/users/nadgowdas/repos","events_url":"https://api.github.com/users/nadgowdas/events{/privacy}","received_events_url":"https://api.github.com/users/nadgowdas/received_events","type":"User","site_admin":false},"created_at":"2015-06-29T05:19:53Z","updated_at":"2015-06-29T05:19:53Z","body":"Would be interesting to see if this can be solved in the new OCF specification with runc\r\nhttps://github.com/docker/docker/pull/14211/files#r33429661\r\n"}}