From 0d5d291c2042cfb39868ee9e5b267ca494f7d637 Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Thu, 5 Oct 2017 17:07:04 -0700 Subject: [PATCH 1/5] Update docs-instances.md to have relevant documentation and exmaples --- pages/docs/user-docs/docs-instances.md | 187 ++++++++++++++++++++----- 1 file changed, 150 insertions(+), 37 deletions(-) diff --git a/pages/docs/user-docs/docs-instances.md b/pages/docs/user-docs/docs-instances.md index abd59b7..4823f6c 100644 --- a/pages/docs/user-docs/docs-instances.md +++ b/pages/docs/user-docs/docs-instances.md @@ -6,7 +6,7 @@ folder: docs toc: false --- -New to Singularity 2.4 is the ability to clone your image, meaning you create an instance of it that has its own namespace. Why would you want to do this? It means that your container can be instantiated and then serve a process that your computer has control of. +Singularity 2.4 introduces the ability to run "container instances", allowing you to run services (*e.g. Nginx, MySQL, etc...*) using Singularity. A container instance, simply put, is a persistant and isolated version of the container image that runs in the background. ## Why container instances? Let's say I want to run a web server. With nginx, that is pretty simple, I install nginx and start the service: @@ -23,8 +23,8 @@ With older versions of Singularity, if you were to do something like this, from You would lose control of the process. It would still be running, but you couldn't kill it. This is a called a ghost process, and it means that for running (enduring) services, Singularity was a no starter. -## Cloning containers -With version 2.4, you can do this in a more realistic way. First, let's put the commands of how to start our service into a script. Let's call it a `startscript`. And we can imagine this fitting into a bootstrap recipe file like this: +## Container Instances in Singularity +With Singularity 2.4 and the addition of container instances, the ability to cleanly, reliably, and safely run services in a container is here. First, let's put the commands of how to start our service into a script. Let's call it a `startscript`. And we can imagine this fitting into a build definition file like this: ``` %startscript @@ -32,76 +32,189 @@ With version 2.4, you can do this in a more realistic way. First, let's put the service nginx start ``` -and an instruction to stop it too: +Now let's say we build a container with that startscript into an image called `nginx.img` and we want to run an nginx service. All we need to do is start the instance and the startscript will get run inside the container automatically: +``` + [command] [image] [name of instance] +$ singularity instance.start nginx.img web +``` + +When we run that command, Singularity creates an isolated environment for the container instances' processes/services to live inside. We can confirm that this command started an instance by running the following command: + +``` +$ singularity instance.list +INSTANCE NAME PID CONTAINER IMAGE +web 790 /home/mibauer/nginx.img +``` + +If we want to run multiple instances from the same image, it's as simple as running the command multiple times. The instance names are an identifier used to uniquely describe an instance, so they cannot be repeated. + +``` +$ singularity instance.start nginx.img web1 +$ singularity instance.start nginx.img web2 +$ singularity instance.start nginx.img web3 +``` + +And again to confirm that the instances are running as we expected: + +``` +$ singularity instance.list +INSTANCE NAME PID CONTAINER IMAGE +web1 790 /home/mibauer/nginx.img +web2 791 /home/mibauer/nginx.img +web3 792 /home/mibauer/nginx.img +``` + +Once an instance is started, the environment inside of that instance will never change. If the service you want to run in your instance requires a bind mount, then you must pass the `-B` option when calling `instance.start`. For example, if you wish to capture the output of the `web1` container instance which is placed at `/output/` inside the container you could do: + +``` +singularity instance.start -B output/dir/outside/:/output/ nginx.img web1 +``` + +## Putting it all together + +In this section, we will demonstrate an example of packaging a service into a container and running it. The service we will be packaging is an API server that converts a web page into a PDF, and can be found [here](https://github.com/alvarcarto/url-to-pdf-api). The final example can be found [here on GitHub](https://github.com/bauerm97/instance-example), and [here on SingularityHub](link-to-shub). If you wish to just download the final image directly from Singularity Hub, simply run `singularity pull shub://bauerm97/instance-example`. + +### Building the Image + +To begin, we need to build the image. When looking at the GitHub page of the `url-to-pdf-api`, we can see that it is a Node 8 server that uses headless Chromium called [Puppeteer](https://github.com/GoogleChrome/puppeteer). Let's first choose a base from which to build our container, in this case I used the docker image `node:8` which comes pre-installed with Node 8: + +``` +Bootstrap: docker +From: node:8 +Includecmd: no +``` + +Puppeteer also requires a few dependencies to be manually installed in addition to Node 8, so we can add those into the `post` section as well as the installation script for the `url-to-pdf-api`: + +``` +%post + apt-get update + apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \ + libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 \ + libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 \ + libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \ + libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \ + fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget curl + rm -r /var/lib/apt/lists/* + cd / + git clone https://github.com/alvarcarto/url-to-pdf-api.git pdf_server + cd pdf_server + npm install + chmod -R 0755 . +``` + +And now we need to define what happens when we start an instance of the container. In this situation, we want to run the commands that starts up the url-to-pdf-api server: ``` %startscript + cd /scif/apps/pdf_server/pdf_server + # Use nohup and /dev/null to completely detach server process from terminal + nohup npm start > /dev/null 2>&1 < /dev/null & +``` + +Also, the `url-to-pdf-api` server requires some environment variables be set, which we can do in the `environment` section: -service nginx stop +``` +%environment + export NODE_ENV=development + export PORT=8000 + export ALLOW_HTTP=true + export URL=localhost ``` -You might even have some special (longer set) of commands in your startscript, if warranted: +Now we can build the definition file into an image! Simply run build and the image will be ready to go: ``` -#!/bin/sh +$ sudo singularity build url-to-pdf-api.img Singularity +``` + +### Running the Server -if [ -z "$OMGTACOSGUNICORN" ]; then - /bin/bash /code/helpers/ctrl/gunicorn.screen - echo "server started, status code $?" -else - echo "server is already running. Use restart or stop." -fi +Now that we have an image, we are ready to start an instance and run the server: -if [ -z "$OMGTACOSCELERY" ]; then - /bin/bash /code/helpers/ctrl/celery.screen - echo "worker started, status code $?" -else - echo "worker is already running. Use restart or stop." -fi +``` +$ singularity instance.start url-to-pdf-api.img pdf ``` -In the above example, there are two services in my container, and based on environment varibles, there is some custom functionality that happens based on how the user sets them upon starting the container instance. +We can confirm it's working by sending the server an http request using curl: -Now let's say we have a container called `nginx.img` and we want to run a service in it. What do we do? Well, first we clone it to make an instance: +``` +$ curl -o google.pdf localhost:8000/api/render?url=http://google.com + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 51664 100 51664 0 0 12443 0 0:00:04 0:00:04 --:--:-- 12446 +``` + +If you shell into the instance, you can see the running processes: ``` - [action] [image] [name of instance] -singularity clone nginx.img instance +$ singularity shell instance://pdf +Singularity: Invoking an interactive shell within container... + +Singularity pdf_server.img:~/bauerm97/instance-example> ps auxf +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +node 87 0.2 0.0 20364 3384 pts/0 S 16:16 0:00 /bin/bash --norc +node 88 0.0 0.0 17496 2144 pts/0 R+ 16:16 0:00 \_ ps auxf +node 1 0.0 0.0 13968 1904 ? Ss 16:10 0:00 singularity-instance: mibauer [pdf] +node 3 0.1 0.4 997452 40364 ? Sl 16:10 0:00 npm +node 13 0.0 0.0 4340 724 ? S 16:10 0:00 \_ sh -c nodemon --watch ./src -e j +node 14 0.0 0.4 1184492 37008 ? Sl 16:10 0:00 \_ node /scif/apps/pdf_server/p +node 26 0.0 0.0 4340 804 ? S 16:10 0:00 \_ sh -c node src/index.js +node 27 0.2 0.5 906108 43424 ? Sl 16:10 0:00 \_ node src/index.js +Singularity pdf_server.img:~/bauerm97/instance-example> ls +LICENSE README.md Singularity out pdf_server.img +Singularity pdf_server.img:~/bauerm97/instance-example> exit ``` -When I do that, I still have my file `nginx.img` sitting on my Desktop, but now you can think about having actually an instance of it running, which I can now control! Heck, I could do that multiple times, if it made sense for my service: +### Making it Pretty + +Now that we have comfirmation that the server is working, let's make it a little cleaner. It's reallying annoying to have to remember the exact curl comand and URL syntax each time you want to request a PDF, so let's automate that. To do that, we're going to be using apps. If you haven't already, check out the [Singularity app documentation](link-to-app-docs-or-scif) to come up to speed. + +First off, we're going to move the installation of the `url-to-pdf-api` into an app, so that there is a designated spot to place output files. To do that, we want to add a section to our definition file to build the server: ``` -singularity clone nginx.img instance1 -singularity clone nginx.img instance2 -singularity clone nginx.img instance3 +%appinstall pdf_server + git clone https://github.com/alvarcarto/url-to-pdf-api.git pdf_server + cd pdf_server + npm install + chmod -R 0755 . ``` -Once you create this instance, you can't do additional things like binds. So if your service requires a special mount or any other kind of connection, do that at the time of the clone: +Now we want to define the pdf_client app, which we will run to send the requests to the server: ``` -singularity clone -B /etc/nginx nginx.img instance1 +%apprun pdf_client + if [ -z "${1:-}" ]; then + echo "Usage: singularity run --app pdf [output file]" + exit 1 + fi + curl -o "/scif/data/pdf/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}" ``` -## Starting Services -Once you have generated instances, you can start them up! You do that with start, directed to the instance name: +As you can see, the `pdf_client` app checks to make sure that the user provides at least one argument. Now that we have an output directory in the container, we need to expose it to the host using a bind mount. Once we've rebuilt the container, make a new directory callout `out` for the generated PDF's to go. Now we simply start the instance like so: ``` -singularity start nginx.img instance1 +$ singularity instance.start -B out/:/scif/data/pdf_client/output/ url-to-pdf-api.img pdf ``` -## Listing Services -You can then easily list services: +And to request a pdf simply do: ``` -singularity list +$ singularity run --app pdf_client instance://pdf http://google.com google.pdf ``` +And to confirm that it works: + +``` +$ ls out/ +google.pdf +``` + + ## Important Notes -- The instances are linked with your user. So if you clone and start with sudo, that is going to go under root, and you will be confused to call `singularity list` as your user and then not see your services. -- The only reason to specify the image is because it could be the case that you have two different images with services named equally. +- The instances are linked with your user. So if you start an instance with sudo, that is going to go under root, and you will need to call `sudo singularity instance.list` in order to see it. This stuff is completely under development and likely to change! Join the conversation!. From 00e8b4e8c30bcfe627f3e2e48081474bb9528b42 Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Thu, 5 Oct 2017 17:44:07 -0700 Subject: [PATCH 2/5] Add joining instance --- pages/docs/user-docs/docs-instances.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pages/docs/user-docs/docs-instances.md b/pages/docs/user-docs/docs-instances.md index 4823f6c..76cc359 100644 --- a/pages/docs/user-docs/docs-instances.md +++ b/pages/docs/user-docs/docs-instances.md @@ -68,9 +68,27 @@ web3 792 /home/mibauer/nginx.img Once an instance is started, the environment inside of that instance will never change. If the service you want to run in your instance requires a bind mount, then you must pass the `-B` option when calling `instance.start`. For example, if you wish to capture the output of the `web1` container instance which is placed at `/output/` inside the container you could do: ``` -singularity instance.start -B output/dir/outside/:/output/ nginx.img web1 +$ singularity instance.start -B output/dir/outside/:/output/ nginx.img web1 ``` +If you want to poke around inside of your instance, you can do a normal `singularity shell` command, but give it the instance URI: + +``` +$ singularity shell instance://web1 +Singularity: Invoking an interactive shell within container... + +Singularity pdf_server.img:~/> +``` + +Similarly, you can use the `singularity run/exec` commands on instances: + +``` +$ singularity run instance://web1 +$ singularity exec instance://web1 ps -ef +``` + +When using `run` with an instance URI, the `runscript` will be executed inside of the instance. Similarly with `exec`, it will execute the given command in the instance. + ## Putting it all together In this section, we will demonstrate an example of packaging a service into a container and running it. The service we will be packaging is an API server that converts a web page into a PDF, and can be found [here](https://github.com/alvarcarto/url-to-pdf-api). The final example can be found [here on GitHub](https://github.com/bauerm97/instance-example), and [here on SingularityHub](link-to-shub). If you wish to just download the final image directly from Singularity Hub, simply run `singularity pull shub://bauerm97/instance-example`. From 9c082f921f238640f806eab13c33e9ec0ab63437 Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Thu, 5 Oct 2017 17:53:16 -0700 Subject: [PATCH 3/5] Few minor tweaks --- pages/docs/user-docs/docs-instances.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/docs/user-docs/docs-instances.md b/pages/docs/user-docs/docs-instances.md index 76cc359..a0647d2 100644 --- a/pages/docs/user-docs/docs-instances.md +++ b/pages/docs/user-docs/docs-instances.md @@ -187,7 +187,7 @@ Singularity pdf_server.img:~/bauerm97/instance-example> exit ### Making it Pretty -Now that we have comfirmation that the server is working, let's make it a little cleaner. It's reallying annoying to have to remember the exact curl comand and URL syntax each time you want to request a PDF, so let's automate that. To do that, we're going to be using apps. If you haven't already, check out the [Singularity app documentation](link-to-app-docs-or-scif) to come up to speed. +Now that we have comfirmation that the server is working, let's make it a little cleaner. It's difficult to remember the exact curl comand and URL syntax each time you want to request a PDF, so let's automate that. To do that, we're going to be using Standard Container Integration Format (SCIF) apps, which are integrated directly into singularity. If you haven't already, check out the [Singularity app documentation](link-to-app-docs-or-scif) to come up to speed. First off, we're going to move the installation of the `url-to-pdf-api` into an app, so that there is a designated spot to place output files. To do that, we want to add a section to our definition file to build the server: @@ -207,7 +207,7 @@ Now we want to define the pdf_client app, which we will run to send the requests echo "Usage: singularity run --app pdf [output file]" exit 1 fi - curl -o "/scif/data/pdf/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}" + curl -o "/scif/data/pdf_client/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}" ``` As you can see, the `pdf_client` app checks to make sure that the user provides at least one argument. Now that we have an output directory in the container, we need to expose it to the host using a bind mount. Once we've rebuilt the container, make a new directory callout `out` for the generated PDF's to go. Now we simply start the instance like so: @@ -222,7 +222,7 @@ And to request a pdf simply do: $ singularity run --app pdf_client instance://pdf http://google.com google.pdf ``` -And to confirm that it works: +And to confirm that it worked: ``` $ ls out/ From 72b73642f919c382a4706a84e7aa6485a091a34a Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Thu, 5 Oct 2017 17:54:43 -0700 Subject: [PATCH 4/5] Remove in development warning --- pages/docs/user-docs/docs-instances.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pages/docs/user-docs/docs-instances.md b/pages/docs/user-docs/docs-instances.md index a0647d2..efb22fb 100644 --- a/pages/docs/user-docs/docs-instances.md +++ b/pages/docs/user-docs/docs-instances.md @@ -232,7 +232,4 @@ google.pdf ## Important Notes -- The instances are linked with your user. So if you start an instance with sudo, that is going to go under root, and you will need to call `sudo singularity instance.list` in order to see it. - - -This stuff is completely under development and likely to change! Join the conversation!. +- The instances are linked with your user. So if you start an instance with sudo, that is going to go under root, and you will need to call `sudo singularity instance.list` in order to see it. \ No newline at end of file From 9548d6fab4b162e2ad216995be39d29818ac0cc7 Mon Sep 17 00:00:00 2001 From: Michael Bauer Date: Thu, 5 Oct 2017 18:07:27 -0700 Subject: [PATCH 5/5] Update documentation to include more accurate code --- pages/docs/user-docs/docs-instances.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pages/docs/user-docs/docs-instances.md b/pages/docs/user-docs/docs-instances.md index efb22fb..aa78351 100644 --- a/pages/docs/user-docs/docs-instances.md +++ b/pages/docs/user-docs/docs-instances.md @@ -126,7 +126,7 @@ And now we need to define what happens when we start an instance of the containe ``` %startscript - cd /scif/apps/pdf_server/pdf_server + cd /pdf_server # Use nohup and /dev/null to completely detach server process from terminal nohup npm start > /dev/null 2>&1 < /dev/null & ``` @@ -135,10 +135,11 @@ Also, the `url-to-pdf-api` server requires some environment variables be set, wh ``` %environment - export NODE_ENV=development - export PORT=8000 - export ALLOW_HTTP=true - export URL=localhost + NODE_ENV=development + PORT=8000 + ALLOW_HTTP=true + URL=localhost + export NODE_ENV PORT ALLOW_HTTP URL ``` Now we can build the definition file into an image! Simply run build and the image will be ready to go: @@ -199,6 +200,15 @@ First off, we're going to move the installation of the `url-to-pdf-api` into an chmod -R 0755 . ``` +And update our `startscript` to point to the app location: + +``` +%startscript + cd "${APPROOT_pdf_server}/pdf_server" + # Use nohup and /dev/null to completely detach server process from terminal + nohup npm start > /dev/null 2>&1 < /dev/null & +``` + Now we want to define the pdf_client app, which we will run to send the requests to the server: ``` @@ -207,7 +217,7 @@ Now we want to define the pdf_client app, which we will run to send the requests echo "Usage: singularity run --app pdf [output file]" exit 1 fi - curl -o "/scif/data/pdf_client/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}" + curl -o "${SINGULARITY_APPDATA}/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}" ``` As you can see, the `pdf_client` app checks to make sure that the user provides at least one argument. Now that we have an output directory in the container, we need to expose it to the host using a bind mount. Once we've rebuilt the container, make a new directory callout `out` for the generated PDF's to go. Now we simply start the instance like so: