├── LICENSE ├── README.md ├── docker.c ├── t ├── Emperor │ ├── vassal_with_docker_socket.ini │ └── vassal_with_standard_socket.ini ├── README ├── emperor.ini ├── tests.py └── truth.py └── uwsgiplugin.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 unbit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | uwsgi-docker 2 | ============ 3 | 4 | An uWSGI plugin for integration with [Docker](https://www.docker.com/). 5 | 6 | This plugin allows you to configure and run vassals in Docker containers. 7 | 8 | Once the Docker daemon is started and you have a bunch of images, you only need to edit a vassal's configuration to dockerize it. 9 | 10 | Requirements 11 | ============ 12 | 13 | At least uWSGI 2.1 for the Emperor and uWSGI 2.0.6 for vassals is required. 14 | 15 | However, for a better experience you should use uWSGI >= 2.1 even in vassals to get advanced features such as binding a socket in the host and passing it to the Docker instance without worrying about redirecting ports or mapping additional filesystems. 16 | 17 | To build the plugin you need libcurl (and its development headers) and libjansson (and its development headers). 18 | 19 | A little understanding of Docker and how it works is highly suggested. 20 | 21 | Only Linux is supported, as it is the only platform supported by Docker. 22 | 23 | Quickstart 24 | ========== 25 | 26 | Build the plugin. The plugin is only required for the Emperor. Vassals are not aware of being dockerized. 27 | 28 | ```sh 29 | uwsgi --build-plugin https://github.com/unbit/uwsgi-docker 30 | ``` 31 | 32 | Ensure the Docker daemon is running. (For example, run it from the terminal as root with `docker -d`.) 33 | 34 | Then prepare your first image. In this example, we'll be preparing a Perl/PSGI app based on Ubuntu. 35 | 36 | ```sh 37 | docker run -t -i --name=psgi_image ubuntu /bin/bash 38 | ``` 39 | 40 | You are now in a new container called "psgi_image", and you can start installing the required packages. In this case, we install a bunch of [CPAN](http://www.cpan.org/) modules, uWSGI itself and a simple Hello World PSGI app. 41 | 42 | ```sh 43 | apt-get update 44 | apt-get install python libperl-dev build-essential libpcre3-dev cpanminus 45 | apt-get clean 46 | cpanm Plack 47 | curl http://uwsgi.it/install | bash -s psgi /usr/bin/uwsgi 48 | mkdir /var/www 49 | echo "my \$app = sub { return [200, [], ['Hello World from Docker']];}" > /var/www/app.pl 50 | ``` 51 | 52 | Then we'll commit the image with the name 'psgi001'. Run the following command in another terminal. 53 | 54 | ```sh 55 | docker commit psgi_image psgi001 56 | ``` 57 | 58 | Now we are free to destroy the "psgi_image" container. 59 | 60 | ```sh 61 | docker stop psgi_image 62 | docker rm psgi_image 63 | ``` 64 | 65 | The "psgi001" image is ready to be used. Let's prepare a vassal for it... 66 | 67 | ```ini 68 | ; the [emperor] section is parsed ONLY by the Emperor 69 | [emperor] 70 | ; use psgi001 as the docker image 71 | docker-image = psgi001 72 | ; map host port 9001 to container port 3031/tcp 73 | docker-port = 9001:3031 74 | ; map host port 127.0.0.1:5001 to container port 5000/tcp 75 | docker-port = 127.0.0.1:5001:5000 76 | 77 | [uwsgi] 78 | psgi = /var/www/app.pl 79 | processes = 4 80 | ; in bridged mode the class B network is allocated, so we can simply bind to the first address starting with 172 81 | ; so we use the very handy .* trick 82 | socket = 172.*:3031 83 | uid = www-data 84 | gid = www-data 85 | ; we are free to bind the stats server to all addresses, as Docker will "firewall" it 86 | stats = :5000 87 | ``` 88 | 89 | Then we'll run the Emperor with the Docker plugin. 90 | 91 | ```ini 92 | [uwsgi] 93 | plugin = path_to/docker_plugin.so 94 | emperor = dir:///etc/uwsgi 95 | ; Just loading the plugin does not force Docker support. 96 | ; This option disallows running vassals without Docker. 97 | emperor-docker-required = true 98 | ; Use /usr/bin/uwsgi as the container entry point (the path could be different from the Emperor one, so we force it) 99 | emperor-wrapper = /usr/bin/uwsgi 100 | 101 | ``` 102 | 103 | Simplyfing socket management (requires uWSGI 2.1 in vassals) 104 | ============================================================ 105 | 106 | One of the most common needs in app management is allowing connections from a frontend web proxy to backend applications. 107 | When running under Docker applications run in a different network namespace, so you'll need some way to enable communications between them. 108 | 109 | In the quickstart example we saw how to forward ports from the host to the container. 110 | You can do the same with UNIX sockets by mounting a host directory (where sockets are bound) to the container. 111 | 112 | Both are easy to accomplish, but are suboptimal: 113 | 114 | * When mounting a directory you will be very probably forced to reveal all of the bound sockets within to the container. This may be a privacy/security problem, especially if you give the sockets a meaningful name. 115 | * When forwarding ports, you need to choose which port is mapped which. This may get cumbersome and may be error-prone and brittle. 116 | * Forwarding ports has its overhead (even if it was all managed in kernel space via iptables). 117 | 118 | To this end, the Docker plugin exposes a `docker-socket` attribute, allowing you to bind a socket in the host and automatically pass it to the Docker instance. Our previous example can be simplified in this way: 119 | 120 | ```ini 121 | ; the [emperor] section is parsed ONLY by the Emperor 122 | [emperor] 123 | ; use psgi001 as the docker image 124 | docker-image = psgi001 125 | ; bind to address /var/run/example.com.socket 126 | docker-socket = /var/run/example.com.socket 127 | 128 | [uwsgi] 129 | psgi = /var/www/app.pl 130 | processes = 4 131 | uid = www-data 132 | gid = www-data 133 | ``` 134 | 135 | The host binds to `/var/run/example.com.socket` and passes it to the dockerized vassal automatically. 136 | 137 | Now all you only need to configure your proxy (Nginx, Apache, etc.) to connect to `/var/run/example.com.socket`. 138 | 139 | You can bind to both UNIX and INET sockets. 140 | 141 | Remember that you need uWSGI 2.1 in the vassal to support socket passing (though it is probable this feature will be backported to uWSGI 2.0.8). 142 | 143 | How it works 144 | ============ 145 | 146 | The plugin communicates with the Docker daemon UNIX socket (`/var/run/docker.sock`). 147 | 148 | On vassal start, the plugin asks the Docker daemon to create a new container with specific attributes and with same name of the vassal (that is, a vassal by the name of "foobar.ini" will generate a Docker container called "foobar.ini"). 149 | This container automatically gets access to an UNIX socket (the proxy Emperor socket) through which it will receive the Emperor's file descriptors (control pipe, configuration pipe and possibly the on-demand socket). 150 | Once the Emperor proxy has passed the file descriptors it is completely destroyed, as from now on the Emperor has full control over the dockerized instance. 151 | 152 | For security and robustness the Docker transaction is managed by a separate process for each vassal. This process is named [uwsgi-docker-bridge] and it requires very little memory. Once this process has completed the spawn of the instance itself, it attaches to the pseudoterminal of the Docker instance, to let you transparently get instance logs if they are not redirected. Once the pseudoterminal closes, the vassal is destroyed too. 153 | 154 | During the startup phase of the vassal, if an existing Docker instance named as the vassal is found, it will be automatically destroyed. 155 | 156 | When the emperor dies, all of the related containers are destroyed too. 157 | 158 | The Emperor Proxy 159 | ================= 160 | 161 | As we saw, the Dockerized instance requires access to a special UNIX socket (the emperor proxy socket) that will pass all of the required file descriptors to the vassal. 162 | 163 | By the default this socket is created in the same directory of the vassal file suffixing it with ".sock". 164 | 165 | That is, "/etc/uwsgi/foobar.ini" will generate "/etc/uwsgi/foobar.ini.sock" that will be mounted as "/foobar.ini.sock" in the container. 166 | 167 | You can override this behavior with the `docker-proxy` attribute. 168 | 169 | ```ini 170 | ; the [emperor] section is parsed ONLY by the Emperor 171 | [emperor] 172 | ; use psgi001 as the docker image 173 | docker-image = psgi001 174 | ; bind to address /var/run/example.com.socket 175 | docker-socket = /var/run/example.com.socket 176 | docker-proxy=/var/run/foobar.sock:/tmp/foosocket 177 | 178 | [uwsgi] 179 | psgi = /var/www/app.pl 180 | processes = 4 181 | uid = www-data 182 | gid = www-data 183 | ``` 184 | 185 | This will create /var/run/foobar.sock in the host, exposed to the docker instance as /tmp/foosocket. 186 | 187 | Remember that the UNIX socket file is removed from the host as soon as the Docker instance has connected to it. 188 | 189 | An important thing **YOU HAVE TO REMEMBER**, is that the uWSGI process run by the instance must have write access to this socket, so if you are using the `docker-user` attribute to start uWSGI as an unprivileged user instead of configuring it for dropping privileges, you will have to tell the emperor to create sockets with a specific permission mode using the classic `chmod-socket` option. 190 | 191 | ```ini 192 | [uwsgi] 193 | plugin = path_to/docker_plugin.so 194 | emperor = dir:///etc/uwsgi 195 | ; once the plugin is loaded, docker support is optional 196 | ; this option disallow running vassals without docker 197 | emperor-docker-required = true 198 | ; use /usr/bin/uwsgi as the container entry point (the path coudl be different from the Emperor one, so we force it) 199 | emperor-wrapper = /usr/bin/uwsgi 200 | chmod-socket = 666 201 | ``` 202 | 203 | Remember the socket is suddenly destroyed after the first connection. 204 | 205 | 206 | Integration with the forkpty router plugin 207 | ========================================== 208 | 209 | [The forkpty router](http://uwsgi-docs.readthedocs.org/en/latest/ForkptyRouter.html) is a plugin included in the official uWSGI sources. It allows you to attach a terminal session to a running uWSGI instance. 210 | 211 | The best approach is exposing a host directory for the vassal in the Docker container. 212 | You will be able to use this directory to create the UNIX socket of the forkpty router. (Another approach would be binding it on a inet socket and forwarding the port.) 213 | 214 | ```ini 215 | ; the [emperor] section is parsed ONLY by the Emperor 216 | [emperor] 217 | ; use psgi001 as the docker image 218 | docker-image = psgi001 219 | ; bind to address /var/run/example.com.socket 220 | docker-socket = /var/run/example.com.socket 221 | 222 | ; mount /pty/foo as /foo (ensure it is owned by www-data) 223 | docker-mount = /pty/foo:/pty 224 | 225 | [uwsgi] 226 | psgi = /var/www/app.pl 227 | processes = 4 228 | uid = www-data 229 | gid = www-data 230 | forkpty-router = /pty/socket 231 | ``` 232 | 233 | Now you can enter the container with: 234 | 235 | ```sh 236 | uwsgi --pty-connect /pty/foo/socket 237 | ``` 238 | 239 | Remember to rebuild uWSGI with `pty` and `forkptyrouter` plugins embedded. You can do this quickly with 240 | 241 | ```sh 242 | UWSGI_EMBED_PLUGINS=pty,forkptyrouter make psgi 243 | ``` 244 | 245 | Technically the host instance only requires `pty`, while the dockerized only requires `forkptyrouter`. 246 | 247 | You can of course also build them as external plugins and load them dynamically. 248 | 249 | ```sh 250 | uwsgi --build-plugin plugins/pty 251 | uwsgi --build-plugin plugins/forkptyrouter 252 | ``` 253 | 254 | By default the forkptyrouter runs `/bin/sh`. `/bin/bash` is likely a better choice -- you may set it with `forkptyrouter-command` option. 255 | 256 | ```ini 257 | ; the [emperor] section is parsed ONLY by the Emperor 258 | [emperor] 259 | ; use psgi001 as the docker image 260 | docker-image = psgi001 261 | ; bind to address /var/run/example.com.socket 262 | docker-socket = /var/run/example.com.socket 263 | 264 | ; mount /pty/foo as /foo (ensure it is owned by www-data) 265 | docker-mount = /pty/foo:/pty 266 | 267 | [uwsgi] 268 | plugin = path_to/forkptyrouter_plugin.so 269 | psgi = /var/www/app.pl 270 | processes = 4 271 | uid = www-data 272 | gid = www-data 273 | forkpty-router = /pty/socket 274 | forkptyrouter-command = /bin/bash 275 | ``` 276 | 277 | The forkptyrouter can be used instead of `nsenter` as it does not require special privileges (your client needs only write access to the specific UNIX socket). 278 | 279 | Attributes 280 | ========== 281 | 282 | * `docker-image` -- set the image to use for the container **(REQUIRED)** 283 | * `docker-socket` -- bind a socket and pass it to the Docker instance 284 | * `docker-port` -- forward a port, syntax `hostip:hostport:dockerport` or `hostport:dockerport` 285 | * `docker-workdir` -- set working directory 286 | * `docker-mount` -- bind mount from host to container, syntax `/host:/docker` 287 | * `docker-network-mode` -- set network mode (bridge, host, none, container:id) 288 | * `docker-hostname` -- set container hostname 289 | * `docker-proxy` -- set emperor proxy path, syntax `hostpath:dockerpath` 290 | * `docker-env` -- set an environment variable in the container, syntax `NAME=VALUE` 291 | * `docker-user` -- run uWSGI in the container as the specified user (otherwise it will start as root and you will need to specify uid and gid in the vassal) 292 | * `docker-memory` -- set the max amount of memory (in bytes) for the container 293 | * `docker-swap` -- set the max amount of swap memory (in bytes) for the container 294 | * `docker-cidfile` -- store the cid (Container ID) file in the specified path 295 | * `docker-dns` -- add a DNS server to the container 296 | 297 | Options 298 | ======= 299 | 300 | * `--docker-emperor/--emperor-docker` -- enable Docker support in the Emperor 301 | * `--docker-emperor-required/--emperor-docker-required` -- enable Docker support in the Emperor and require each vassal to expose Docker options 302 | * `--docker-debug` -- enable debug logging 303 | * `--docker-daemon-socket` -- change the default Docker daemon socket (default `/var/run/docker.sock`) 304 | 305 | Tips & Tricks 306 | ============= 307 | 308 | Enabling the Emperor stats server (--emperor-stats) will allow you to inspect all of the vassals attributes (included docker ones) 309 | 310 | All of the imperial monitors supporting attributes (currently dir, glob and mongodb) will work with this plugin. 311 | 312 | Known Issues 313 | ============ 314 | 315 | * Only .ini files are supported (must be fixed in uWSGI itself) 316 | * Currently configs are piped via the emperor proxy socket, but allowing the dockerized instance to access the original file should be permitted (now config piping is hardcoded) 317 | -------------------------------------------------------------------------------- /docker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern struct uwsgi_server uwsgi; 6 | 7 | #define DOCKER_SOCKET "/var/run/docker.sock" 8 | #define DOCKER_API "1.14" 9 | 10 | static struct uwsgi_docker { 11 | int emperor; 12 | int debug; 13 | int emperor_required; 14 | char *socket; 15 | char *vassal_socket_dir; 16 | } udocker; 17 | 18 | static struct uwsgi_option docker_options[] = { 19 | {"docker-emperor", no_argument, 0, "enable Emperor integration with docker", uwsgi_opt_true, &udocker.emperor, 0}, 20 | {"emperor-docker", no_argument, 0, "enable Emperor integration with docker", uwsgi_opt_true, &udocker.emperor, 0}, 21 | {"docker-emperor-required", no_argument, 0, "enable Emperor integration with docker", uwsgi_opt_true, &udocker.emperor_required, 0}, 22 | {"emperor-docker-required", no_argument, 0, "enable Emperor integration with docker", uwsgi_opt_true, &udocker.emperor_required, 0}, 23 | {"docker-debug", no_argument, 0, "enable debug mode", uwsgi_opt_true, &udocker.debug, 0}, 24 | {"docker-daemon-socket", required_argument, 0, "set the docker daemon socket path (default: " DOCKER_SOCKET ")", uwsgi_opt_set_str, &udocker.socket, 0}, 25 | {"docker-socket-dir", required_argument, 0, "set default vassal socket directory", uwsgi_opt_set_str, &udocker.vassal_socket_dir, 0}, 26 | UWSGI_END_OF_OPTIONS 27 | }; 28 | 29 | // hack for adding support for unix sockets to libcurl 30 | static curl_socket_t docker_unix_socket(void *foobar, curlsocktype cs_type, struct curl_sockaddr *c_addr) { 31 | struct sockaddr_un* un_addr = (struct sockaddr_un*)&c_addr->addr; 32 | c_addr->family = AF_UNIX; 33 | c_addr->addrlen = sizeof(struct sockaddr_un); 34 | 35 | memset(un_addr, 0, c_addr->addrlen); 36 | un_addr->sun_family = AF_UNIX; 37 | strncpy(un_addr->sun_path, udocker.socket, sizeof(un_addr->sun_path)); 38 | c_addr->protocol = 0; 39 | return socket(c_addr->family, c_addr->socktype, c_addr->protocol); 40 | } 41 | 42 | // store a libcurl response in a uwsgi_buffer 43 | static size_t docker_response(void *ptr, size_t size, size_t nmemb, void *data) { 44 | struct uwsgi_buffer *ub = (struct uwsgi_buffer *) data; 45 | if (uwsgi_buffer_append(ub, ptr, size*nmemb)) return -1; 46 | return size*nmemb; 47 | } 48 | 49 | static json_t *docker_json(char *method, char *url, json_t *json, long *http_status) { 50 | json_t *response = NULL; 51 | char *json_body = NULL; 52 | struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size); 53 | CURL *curl = curl_easy_init(); 54 | if (!curl) goto error; 55 | struct curl_slist *headers = NULL; 56 | headers = curl_slist_append(headers, "Content-Type: application/json"); 57 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, uwsgi.socket_timeout); 58 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, uwsgi.socket_timeout); 59 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 60 | char *full_url = uwsgi_concat2("http://127.0.0.1", url); 61 | curl_easy_setopt(curl, CURLOPT_URL, full_url); 62 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); 63 | if (json) { 64 | json_body = json_dumps(json, 0); 65 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body); 66 | } 67 | curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, docker_unix_socket); 68 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, docker_response); 69 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, ub); 70 | CURLcode res = curl_easy_perform(curl); 71 | if (json_body) free(json_body); 72 | 73 | if (res != CURLE_OK) { 74 | uwsgi_log("[docker] error sending request %s: %s\n", full_url, curl_easy_strerror(res)); 75 | free(full_url); 76 | curl_slist_free_all(headers); 77 | curl_easy_cleanup(curl); 78 | goto error; 79 | } 80 | 81 | curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, http_status); 82 | curl_slist_free_all(headers); 83 | curl_easy_cleanup(curl); 84 | 85 | if (udocker.debug) { 86 | uwsgi_log("[docker-debug] HTTP request to %s -> %d\n%.*s\n", full_url, *http_status, ub->pos, ub->buf); 87 | } 88 | 89 | free(full_url); 90 | 91 | json_error_t error; 92 | response = json_loadb(ub->buf, ub->pos, 0, &error); 93 | error: 94 | uwsgi_buffer_destroy(ub); 95 | return response; 96 | } 97 | 98 | // check if a container has teh specified name 99 | static int docker_hasname(json_t *obj, char *container_name) { 100 | json_t *names = json_object_get(obj, "Names"); 101 | if (!names || !json_is_array(names)) { 102 | return 0; 103 | } 104 | size_t i, items = json_array_size(names); 105 | for(i=0;iname); 196 | // wait for connection 197 | uwsgi_master_manage_emperor_proxy(proxy_fd, ui->pipe[1], ui->pipe_config[1], socket_fd); 198 | // we do not need those fds anymore 199 | close(proxy_fd); 200 | close(ui->pipe[1]); 201 | if (ui->pipe_config[1] > -1) 202 | close(ui->pipe_config[1]); 203 | if (socket_fd > -1) 204 | close(socket_fd); 205 | unlink(proxy_path); 206 | 207 | int fd = uwsgi_connect(udocker.socket, uwsgi.socket_timeout, 0); 208 | if (fd < 0) goto end; 209 | 210 | // send a raw request, we do not need curl this time 211 | // ub structure us not freed on error, as we brutally exit 212 | struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size); 213 | if (uwsgi_buffer_append(ub, "POST /containers/", 17)) goto end; 214 | if (uwsgi_buffer_append(ub, container_id, strlen(container_id))) goto end; 215 | if (uwsgi_buffer_append(ub, "/attach?stream=1&logs=1&stdin=1&stdout=1&stderr=1 HTTP/1.1\r\n\r\n", 62)) goto end; 216 | if (uwsgi_write_nb(fd, ub->buf, ub->pos, uwsgi.socket_timeout)) { 217 | uwsgi_error("docker_attach()/write()"); 218 | goto end; 219 | } 220 | uwsgi_buffer_destroy(ub); 221 | 222 | // now start waiting for pty data 223 | for(;;) { 224 | int ret = uwsgi_waitfd_event(fd, -1, POLLIN); 225 | if (ret <= 0) break; 226 | char buf[8192]; 227 | ssize_t rlen = read(fd, buf, 8192); 228 | if (rlen <= 0) { 229 | uwsgi_error("docker_attach()/read()"); 230 | break; 231 | } 232 | // forward the log line 233 | uwsgi_log("%.*s", rlen, buf); 234 | } 235 | end: 236 | // destroy the container 237 | uwsgi_log("[docker] destroying container %s (%s) ...\n", container_id, ui->name); 238 | docker_destroy(ui->name, container_id); 239 | // never here 240 | } 241 | 242 | static int docker_add_item_to_array(struct uwsgi_instance *ui, char *value, void *data) { 243 | json_t *array = (json_t *) data; 244 | json_array_append(array, json_string(value)); 245 | return 0; 246 | } 247 | 248 | static int docker_add_port(struct uwsgi_instance *ui, char *value, void *data) { 249 | int ret = -1; 250 | json_t *ports = (json_t *) data; 251 | // how many colons ? 252 | size_t i,n = 0; 253 | char **items = uwsgi_split_quoted(value, strlen(value), ":", &n); 254 | if (n < 2) goto end; 255 | json_t *port_map_list = json_array(); 256 | json_t *port_map = json_object(); 257 | // hostport:dockerport ? 258 | char *docker_port = NULL; 259 | if (n == 2) { 260 | docker_port = items[1]; 261 | json_object_set(port_map, "HostPort", json_string(items[0])); 262 | } 263 | // hostip:hostport:dockerport 264 | else { 265 | docker_port = items[2]; 266 | json_object_set(port_map, "HostIp", json_string(items[0])); 267 | json_object_set(port_map, "HostPort", json_string(items[1])); 268 | } 269 | json_array_append(port_map_list, port_map); 270 | json_object_set(ports, docker_port, port_map_list); 271 | ret = 0; 272 | end: 273 | for(i=0;iname); 316 | if (udocker.emperor_required) { 317 | exit(1); 318 | } 319 | return; 320 | } 321 | 322 | char *processname = uwsgi_concat2("[uwsgi-docker-bridge] ", ui->name); 323 | uwsgi_set_processname(processname); 324 | free(processname); 325 | 326 | char *proxy_attr_emperor = NULL; 327 | char *proxy_attr_docker = NULL; 328 | if (proxy_attr) { 329 | char *colon = strchr(proxy_attr, ':'); 330 | if (colon) { 331 | *colon = 0; 332 | // we leak this, sorry :) 333 | proxy_attr_emperor = uwsgi_str(proxy_attr); 334 | *colon = ':'; 335 | proxy_attr_docker = colon+1; 336 | } 337 | else { 338 | proxy_attr_emperor = proxy_attr; 339 | proxy_attr_docker = proxy_attr; 340 | } 341 | } 342 | else { 343 | // if docker-proxy is not specified we place the socket 344 | // in the vassals dir 345 | char *socket_path = uwsgi_expand_path(ui->name, strlen(ui->name), NULL); 346 | 347 | if (!socket_path && udocker.vassal_socket_dir) { 348 | // Instance name is probably not a file. 349 | // Let's place socket in vassal_socket_dir 350 | socket_path = uwsgi_concat3(udocker.vassal_socket_dir, "/", ui->name); 351 | } 352 | 353 | if (!socket_path) { 354 | uwsgi_log("[docker] unable to build proxy socket path\n"); 355 | exit(1); 356 | } 357 | proxy_attr_emperor = uwsgi_concat2(socket_path, ".sock"); 358 | free(socket_path); 359 | proxy_attr_docker = uwsgi_concat3("/", ui->name, ".sock"); 360 | } 361 | 362 | int socket_fd = -1; 363 | char *docker_socket = vassal_attr_get(ui, "docker-socket"); 364 | if (docker_socket) { 365 | char *tcp_port = strchr(docker_socket, ':'); 366 | if (tcp_port) { 367 | socket_fd = bind_to_tcp(docker_socket, uwsgi.listen_queue, tcp_port); 368 | } 369 | else { 370 | socket_fd = bind_to_unix(docker_socket, uwsgi.listen_queue, uwsgi.chmod_socket, 0); 371 | } 372 | if (socket_fd == -1) { 373 | uwsgi_error("error binding docker-socket"); 374 | exit(1); 375 | } 376 | } 377 | 378 | // first of all we wait for proxy connection 379 | int proxy_fd = bind_to_unix(proxy_attr_emperor, uwsgi.listen_queue, uwsgi.chmod_socket, 0); 380 | if (proxy_fd < 0) exit(1); 381 | 382 | // start connecting to the docker server in sync way 383 | json_t *root = json_object(); 384 | if (!root) exit(1); 385 | 386 | if (json_object_set(root, "Image", json_string(image_attr))) exit(1); 387 | 388 | if (json_object_set(root, "AttachStdin", json_true())) exit(1); 389 | if (json_object_set(root, "OpenStdin", json_true())) exit(1); 390 | if (json_object_set(root, "Tty", json_true())) exit(1); 391 | if (json_object_set(root, "AttachStdout", json_true())) exit(1); 392 | if (json_object_set(root, "AttachStderr", json_true())) exit(1); 393 | 394 | char *docker_workdir = vassal_attr_get(ui, "docker-workdir"); 395 | if (docker_workdir) { 396 | if (json_object_set(root, "WorkingDir", json_string(docker_workdir))) exit(1); 397 | } 398 | 399 | char *docker_hostname = vassal_attr_get(ui, "docker-hostname"); 400 | if (docker_hostname) { 401 | if (json_object_set(root, "Hostname", json_string(docker_hostname))) exit(1); 402 | } 403 | 404 | char *docker_memory = vassal_attr_get(ui, "docker-memory"); 405 | if (docker_memory) { 406 | if (json_object_set(root, "Memory", json_integer(strtoul(docker_memory, NULL, 10)))) exit(1); 407 | } 408 | 409 | char *docker_swap = vassal_attr_get(ui, "docker-swap"); 410 | if (docker_swap) { 411 | if (json_object_set(root, "MemorySwap", json_integer(strtoul(docker_swap, NULL, 10)))) exit(1); 412 | } 413 | 414 | char *docker_user = vassal_attr_get(ui, "docker-user"); 415 | if (docker_user) { 416 | if (json_object_set(root, "User", json_string(docker_user))) exit(1); 417 | } 418 | 419 | // Env 420 | json_t *env = json_array(); 421 | if (vassal_attr_get_multi(ui, "docker-env", docker_add_item_to_array, env)) { 422 | uwsgi_log("[docker] unable to build envs list for vassal %s\n", ui->name); 423 | exit(1); 424 | } 425 | char *env_proxy = uwsgi_concat2("UWSGI_EMPEROR_PROXY=", proxy_attr_docker); 426 | json_array_append(env, json_string(env_proxy)); 427 | free(env_proxy); 428 | if (json_object_set(root, "Env", env)) exit(1); 429 | 430 | json_t *ports = json_object(); 431 | if (vassal_attr_get_multi(ui, "docker-port", docker_expose_port, ports)) { 432 | uwsgi_log("[docker] unable to build port mapping for vassal %s\n", ui->name); 433 | exit(1); 434 | } 435 | if (json_object_set(root, "ExposedPorts", ports)) exit(1); 436 | 437 | json_t *cmd = json_array(); 438 | if (!cmd) exit(1); 439 | 440 | char *arg = *argv++; 441 | while(arg) { 442 | json_array_append(cmd, json_string(arg)); 443 | arg = *argv++; 444 | } 445 | 446 | if (json_object_set(root, "Cmd", cmd)) exit(1); 447 | 448 | char *container_id = NULL; 449 | json_t *garbage = NULL; 450 | 451 | for(;;) { 452 | long http_status = 0; 453 | 454 | char *url = uwsgi_concat2("/containers/create?name=", ui->name); 455 | if (udocker.debug) { 456 | uwsgi_log("[docker-debug] POST %s\n%s\n", url, json_dumps(root, 0)); 457 | } 458 | json_t *response = docker_json("POST", url, root, &http_status); 459 | free(url); 460 | 461 | // if the status code is 409 (Conflict), the container is already running, 462 | // destroy it and retry 463 | if (http_status == 409) { 464 | // if we cannot destroy the container 465 | // we better to quit 466 | if (docker_destroy(ui->name, NULL)) { 467 | exit(1); 468 | } 469 | // re-create it 470 | if (response) json_decref(response); 471 | continue; 472 | } 473 | 474 | if (http_status != 201) { 475 | uwsgi_log("[docker] unable to create container for vassal %s\n", ui->name); 476 | exit(1); 477 | } 478 | 479 | if (!response) exit(1); 480 | 481 | json_t *json_container_id = json_object_get(response, "Id"); 482 | if (!json_container_id) { 483 | uwsgi_log("[docker] cannot find container id for vassal %s\n", ui->name); 484 | exit(1); 485 | } 486 | if (!json_is_string(json_container_id)) { 487 | uwsgi_log("[docker] invalid container id for vassal %s\n", ui->name); 488 | exit(1); 489 | } 490 | 491 | container_id = (char *) json_string_value(json_container_id); 492 | if (!container_id) { 493 | uwsgi_log("[docker] invalid container id for vassal %s\n", ui->name); 494 | exit(1); 495 | } 496 | 497 | garbage = response; 498 | break; 499 | } 500 | 501 | char *docker_cidfile = vassal_attr_get(ui, "docker-cidfile"); 502 | if (docker_cidfile) { 503 | FILE *cidfile = fopen(docker_cidfile, "w"); 504 | if (!cidfile) { 505 | uwsgi_error_open(docker_cidfile); 506 | exit(1); 507 | } 508 | if (fprintf(cidfile, "%s\n", container_id) < 2) { 509 | uwsgi_error("docker_run()/fprintf()"); 510 | exit(1); 511 | } 512 | fclose(cidfile); 513 | } 514 | 515 | // free json object 516 | json_decref(root); 517 | // and now we sart the docker instance 518 | root = json_object(); 519 | 520 | // Volumes/Binds 521 | json_t *binds = json_array(); 522 | if (vassal_attr_get_multi(ui, "docker-mount", docker_add_item_to_array, binds)) { 523 | uwsgi_log("[docker] unable to build volumes mapping for vassal %s\n", ui->name); 524 | exit(1); 525 | } 526 | char *proxy_bind = uwsgi_concat3(proxy_attr_emperor, ":", proxy_attr_docker); 527 | json_array_append(binds, json_string(proxy_bind)); 528 | free(proxy_bind); 529 | json_object_set(root, "Binds", binds); 530 | 531 | // Dns 532 | json_t *dns = json_array(); 533 | if (vassal_attr_get_multi(ui, "docker-dns", docker_add_item_to_array, dns)) { 534 | uwsgi_log("[docker] unable to build dns list for vassal %s\n", ui->name); 535 | exit(1); 536 | } 537 | json_object_set(root, "Dns", dns); 538 | 539 | 540 | // PortBindings 541 | ports = json_object(); 542 | if (vassal_attr_get_multi(ui, "docker-port", docker_add_port, ports)) { 543 | uwsgi_log("[docker] unable to build port mapping for vassal %s\n", ui->name); 544 | exit(1); 545 | } 546 | if (json_object_set(root, "PortBindings", ports)) exit(1); 547 | 548 | char *docker_network_mode = vassal_attr_get(ui, "docker-network-mode"); 549 | if (docker_network_mode) { 550 | if (json_object_set(root, "NetworkMode", json_string(docker_network_mode))) exit(1); 551 | } 552 | 553 | long http_status = 0; 554 | char *url = uwsgi_concat3("/containers/", container_id, "/start"); 555 | if (udocker.debug) { 556 | uwsgi_log("[docker-debug] POST %s\n%s\n", url, json_dumps(root, 0)); 557 | } 558 | json_t *response = docker_json("POST", url, root, &http_status); 559 | free(url); 560 | if (http_status != 204) { 561 | uwsgi_log("[docker] unable to start container %s (%s)\n", container_id, ui->name); 562 | exit(1); 563 | } 564 | 565 | if (response) json_decref(response); 566 | 567 | // if garbage is defined, container_id is part of a json object 568 | // this may seems useless, but we want to have the minimal impact 569 | // for the docker bridge 570 | if (garbage) { 571 | container_id = uwsgi_str(container_id); 572 | json_decref(garbage); 573 | } 574 | 575 | uwsgi_log("[docker] started %s (%d)\n", container_id, http_status); 576 | 577 | // free json object 578 | json_decref(root); 579 | 580 | // now attach to stdout and stderr (read: pty) 581 | docker_attach(ui, proxy_fd, proxy_attr_emperor, container_id, socket_fd); 582 | // never here ? 583 | exit(0); 584 | } 585 | 586 | static void docker_setup(int (*start)(void *), char **argv) { 587 | if (udocker.emperor_required) udocker.emperor = 1; 588 | if (udocker.emperor) { 589 | // TODO this should be an option 590 | uwsgi.emperor_force_config_pipe = 1; 591 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-proxy"); 592 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-image"); 593 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-port"); 594 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-socket"); 595 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-workdir"); 596 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-hostname"); 597 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-memory"); 598 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-swap"); 599 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-mount"); 600 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-dns"); 601 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-env"); 602 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-user"); 603 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-cidfile"); 604 | uwsgi_string_new_list(&uwsgi.emperor_collect_attributes, "docker-network-mode"); 605 | } 606 | if (!udocker.socket) { 607 | udocker.socket = DOCKER_SOCKET; 608 | } 609 | } 610 | 611 | struct uwsgi_plugin docker_plugin = { 612 | .name = "docker", 613 | .options = docker_options, 614 | // jail is the best hook to set options before the Emperor spawns 615 | .jail = docker_setup, 616 | .vassal_before_exec = docker_run, 617 | }; 618 | -------------------------------------------------------------------------------- /t/Emperor/vassal_with_docker_socket.ini: -------------------------------------------------------------------------------- 1 | ; This configuration run in both user and root mode 2 | ; the [emperor] section is parsed ONLY by the Emperor 3 | [emperor] 4 | ; use psgi001 as the docker image 5 | docker-image = psgi001 6 | ; bind to address /var/run/example.com.socket 7 | docker-socket = /tmp/vassal_with_docker_socket_docker.socket 8 | ; manually set the docker proxy 9 | ; Note: if you run this in /vagrant folder, you have to use a custom docker proxy! 10 | docker-proxy=/tmp/vassal_with_docker_socket.sock:/tmp/vassal_with_docker_socket 11 | 12 | [uwsgi] 13 | psgi = /var/www/app.pl 14 | processes = 4 15 | uid = www-data 16 | gid = www-data 17 | -------------------------------------------------------------------------------- /t/Emperor/vassal_with_standard_socket.ini: -------------------------------------------------------------------------------- 1 | ; the [emperor] section is parsed ONLY by the Emperor 2 | [emperor] 3 | ; use psgi001 as the docker image 4 | docker-image = psgi001 5 | ; map host port 9001 to container port 3031/tcp 6 | docker-port = 9001:3031 7 | ; map host port 127.0.0.1:5001 to container port 5000/tcp 8 | docker-port = 127.0.0.1:5001:5000 9 | docker-proxy=/tmp/vassal_with_standard_socket.sock:/tmp/vassal_with_standard_socket 10 | ; mount /pty/foo as /foo (ensure it is owned by www-data) 11 | docker-mount = /pty/foo:/pty 12 | 13 | [uwsgi] 14 | psgi = /var/www/app.pl 15 | processes = 4 16 | ; in bridged mode the class B network is allocated, so we can simply bind to the first address starting with 172 17 | ; so we use the very handy .* trick 18 | socket = :3031 19 | uid = www-data 20 | gid = www-data 21 | ; we are free to bind the stats server to all addresses, as Docker will "firewall" it 22 | stats = :5000 23 | forkpty-router = /pty/socket 24 | forkptyrouter-command = /bin/bash 25 | chmod-socket = 666 26 | -------------------------------------------------------------------------------- /t/README: -------------------------------------------------------------------------------- 1 | REQUIREMENTS: 2 | ============ 3 | * uwsgi version >= 2.1.0 in your system path, for example with enabled plugins pty and forkptyrouter: 4 | (sudo) UWSGI_BIN_NAME=/usr/bin/uwsgi UWSGI_EMBED_PLUGINS=pty,forkptyrouter make 5 | 6 | * docker image named psgi001 (configured similar to plugin-docs) but running an uwsgi version >= 2.1.0. 7 | TODO: could add a Dockerfile to automate the image building. 8 | 9 | * docker-py (install it with pip) 10 | 11 | * pty folder correctly configured on both the host and the container. Specifically: 12 | /pty/foo folder writable by user on host 13 | /pty folder writable by www-data on container 14 | 15 | You'd be able to run uwsgi both in non-privileged and root mode. 16 | Note: if you want to run in non-privileged mode, your user should be part of the docker group (in order to be able to manage docker without being root). 17 | 18 | If you need to change something in the .ini files, remember to change the related "truth" otherwise tests will fail. 19 | 20 | 21 | TESTING: 22 | =========== 23 | Run uwsgi from the plugin directory: 24 | uwsgi t/emperor.ini 25 | And then, in another shell, run the tests: 26 | python t/tests.py 27 | -------------------------------------------------------------------------------- /t/emperor.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | plugin = ./docker_plugin.so 3 | emperor = dir:///%d/Emperor 4 | ; Just loading the plugin does not force Docker support. 5 | ; This option disallows running vassals without Docker. 6 | emperor-docker-required = true 7 | ; Use /usr/bin/uwsgi as the container entry point (the path could be different from the Emperor one, so we force it) 8 | emperor-wrapper = /usr/bin/uwsgi 9 | -------------------------------------------------------------------------------- /t/tests.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding = utf-8 3 | 4 | import unittest 5 | import socket 6 | from time import sleep 7 | 8 | try: 9 | import docker 10 | except ImportError: 11 | from sys import exit 12 | print("You need docker-py module in order to run these tests.") 13 | exit() 14 | 15 | from truth import CONTAINERS_TRUTH 16 | 17 | 18 | class TestDockerPlugin(unittest.TestCase): 19 | 20 | def assertDictionariesSubset(self, a, b, msg=None): 21 | if not msg: 22 | msg = "the dictionaries are not contained one in the other" 23 | 24 | if type(a) != dict or type(b) != dict: 25 | raise self.failureException(msg) 26 | 27 | m, n = (a, b) if len(a) <= len(b) else (b, a) 28 | 29 | for k in m: 30 | try: 31 | # Nested dictionaries 32 | if type(m[k]) == dict: 33 | try: 34 | self.assertDictionariesSubset(m[k], n[k]) 35 | except self.failureException: 36 | print("Error on %s" % k) 37 | raise 38 | # Every other field 39 | else: 40 | if not m[k] == n[k]: 41 | print("Error on %s" % k) 42 | raise self.failureException(msg) 43 | except KeyError: 44 | raise self.failureException(msg) 45 | 46 | return True 47 | 48 | def setUp(self): 49 | self.client = docker.Client(base_url='unix://var/run/docker.sock', 50 | version='1.12', 51 | timeout=10) 52 | self.addTypeEqualityFunc(dict, 'assertDictionariesSubset') 53 | 54 | def test_running_containers(self): 55 | containers = self.client.containers(quiet=False, all=False, trunc=True, latest=False, since=None, 56 | before=None, limit=-1) 57 | 58 | self.assertGreaterEqual(len(containers), len(CONTAINERS_TRUTH)) 59 | 60 | count = 0 61 | for c in containers: 62 | c_name = c[u'Names'][0] 63 | inspected_container = self.client.inspect_container(c) 64 | 65 | try: 66 | self.assertEqual( 67 | CONTAINERS_TRUTH[c_name], inspected_container) 68 | count += 1 69 | except KeyError: 70 | # It's ok, there could be other containers running 71 | pass 72 | 73 | if c_name == u'/vassal_with_standard_socket.ini': 74 | # Check the pty router 75 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 76 | # Connect to the socket (mind the permission on the FS!) 77 | sock.connect('/pty/foo/socket') 78 | # Rcv from the socket 79 | prompt, a = sock.recvfrom(128) # 128 should be enough 80 | # Assert the address and that the shell prompt is not '' 81 | self.assertEqual(a, '/pty/socket') 82 | self.assertTrue(prompt) 83 | 84 | # Try sending a command 85 | sock.sendall("whoami\n") 86 | sleep(0.1) 87 | user, _ = sock.recvfrom(128) 88 | self.assertEqual('whoami\r\nwww-data\r\n' + prompt, user) 89 | 90 | self.assertEqual(count, len(CONTAINERS_TRUTH)) 91 | 92 | 93 | unittest.main() 94 | -------------------------------------------------------------------------------- /t/truth.py: -------------------------------------------------------------------------------- 1 | CONTAINERS_TRUTH = { 2 | u'/vassal_with_standard_socket.ini': {u'Args': [u'--ini', u'emperor://vassal_with_standard_socket.ini'], 3 | u'Config': {u'AttachStderr': True, 4 | u'AttachStdin': True, 5 | u'AttachStdout': True, 6 | u'Cmd': [u'/usr/bin/uwsgi', 7 | u'--ini', 8 | u'emperor://vassal_with_standard_socket.ini'], 9 | u'CpuShares': 0, 10 | u'Cpuset': u'', 11 | u'Domainname': u'', 12 | u'Entrypoint': None, 13 | u'Env': [u'UWSGI_EMPEROR_PROXY=/tmp/vassal_with_standard_socket', 14 | u'HOME=/', 15 | u'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 16 | u'ExposedPorts': {u'3031': {}, u'5000': {}}, 17 | u'Image': u'psgi001', 18 | u'NetworkDisabled': False, 19 | u'OnBuild': None, 20 | u'OpenStdin': True, 21 | u'PortSpecs': None, 22 | u'StdinOnce': False, 23 | u'Tty': True, 24 | u'User': u'', 25 | u'Volumes': None, 26 | u'WorkingDir': u''}, 27 | u'Driver': u'aufs', 28 | u'HostConfig': {u'Binds': [u'/pty/foo:/pty', 29 | u'/tmp/vassal_with_standard_socket.sock:/tmp/vassal_with_standard_socket'], 30 | u'ContainerIDFile': u'', 31 | u'Dns': [], 32 | u'DnsSearch': None, 33 | u'Links': None, 34 | u'LxcConf': None, 35 | u'NetworkMode': u'', 36 | u'PortBindings': {u'3031': [{u'HostIp': u'0.0.0.0', u'HostPort': u'9001'}], 37 | u'5000': [{u'HostIp': u'127.0.0.1', u'HostPort': u'5001'}]}, 38 | u'Privileged': False, 39 | u'PublishAllPorts': False, 40 | u'VolumesFrom': None}, 41 | u'MountLabel': u'', 42 | u'Name': u'/vassal_with_standard_socket.ini', 43 | u'NetworkSettings': {u'Bridge': u'docker0', 44 | u'Gateway': u'172.17.42.1', 45 | u'IPPrefixLen': 16, 46 | u'PortMapping': None, 47 | u'Ports': {u'3031': [{u'HostIp': u'0.0.0.0', u'HostPort': u'9001'}], 48 | u'5000': [{u'HostIp': u'127.0.0.1', u'HostPort': u'5001'}]}}, 49 | u'Path': u'/usr/bin/uwsgi', 50 | u'ProcessLabel': u'', 51 | u'ResolvConfPath': u'/etc/resolv.conf', 52 | u'Volumes': {u'/pty': u'/pty/foo', 53 | u'/tmp/vassal_with_standard_socket': u'/tmp/vassal_with_standard_socket.sock'}, 54 | u'VolumesRW': {u'/pty': True, u'/tmp/vassal_with_standard_socket': True}}, 55 | u'/vassal_with_docker_socket.ini': {u'Args': [u'--ini', u'emperor://vassal_with_docker_socket.ini'], 56 | u'Config': {u'AttachStderr': True, 57 | u'AttachStdin': True, 58 | u'AttachStdout': True, 59 | u'Cmd': [u'/usr/bin/uwsgi', 60 | u'--ini', 61 | u'emperor://vassal_with_docker_socket.ini'], 62 | u'CpuShares': 0, 63 | u'Cpuset': u'', 64 | u'Domainname': u'', 65 | u'Entrypoint': None, 66 | u'Env': [u'UWSGI_EMPEROR_PROXY=/tmp/vassal_with_docker_socket', 67 | u'HOME=/', 68 | u'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 69 | u'ExposedPorts': None, 70 | u'Image': u'psgi001', 71 | u'NetworkDisabled': False, 72 | u'OnBuild': None, 73 | u'OpenStdin': True, 74 | u'PortSpecs': None, 75 | u'StdinOnce': False, 76 | u'Tty': True, 77 | u'User': u'', 78 | u'Volumes': None, 79 | u'WorkingDir': u''}, 80 | u'Driver': u'aufs', 81 | u'HostConfig': {u'Binds': [u'/tmp/vassal_with_docker_socket.sock:/tmp/vassal_with_docker_socket'], 82 | u'ContainerIDFile': u'', 83 | u'Dns': [], 84 | u'DnsSearch': None, 85 | u'Links': None, 86 | u'LxcConf': None, 87 | u'NetworkMode': u'', 88 | u'PortBindings': {}, 89 | u'Privileged': False, 90 | u'PublishAllPorts': False, 91 | u'VolumesFrom': None}, 92 | u'MountLabel': u'', 93 | u'Name': u'/vassal_with_docker_socket.ini', 94 | u'NetworkSettings': {u'Bridge': u'docker0', 95 | u'Gateway': u'172.17.42.1', 96 | u'IPPrefixLen': 16, 97 | u'PortMapping': None, 98 | u'Ports': {}}, 99 | u'Path': u'/usr/bin/uwsgi', 100 | u'ProcessLabel': u'', 101 | u'ResolvConfPath': u'/etc/resolv.conf', 102 | u'Volumes': {u'/tmp/vassal_with_docker_socket': u'/tmp/vassal_with_docker_socket.sock'}, 103 | u'VolumesRW': {u'/tmp/vassal_with_docker_socket': True}} 104 | } 105 | -------------------------------------------------------------------------------- /uwsgiplugin.py: -------------------------------------------------------------------------------- 1 | NAME='docker' 2 | LIBS=['-lcurl', '-ljansson'] 3 | GCC_LIST=['docker'] 4 | --------------------------------------------------------------------------------