├── LICENSE ├── README.md ├── images ├── add_to_project_jupyter_notebook.png ├── attach_compute_cluster.png ├── attach_storage_notebook.png ├── create_jupyter_builder.png ├── create_jupyter_cluster.png ├── create_jupyter_notebook.png ├── deployments_attach_storage_notebook.png ├── ipyparallel_client.png ├── overview_notebook.png └── scale_up_compute_engine.png ├── notebooks └── ipyparallel-testing │ ├── cluster-test.ipynb │ └── requirements.txt ├── openshift ├── images.json └── templates.json └── scripts └── generate-images.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Graham Dumpleton 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # S2I Enabled Jupyter Notebook Images 2 | 3 | This repository provides templates and documentation for deploying [Source-to-Image](https://github.com/openshift/source-to-image) (S2I) enabled [Jupyter Notebook](http://jupyter.org) images for Python on OpenShift. 4 | 5 | ## Comparison to Jupyter Project Images 6 | 7 | The Jupyter project provides Docker-formatted container images via their [GitHub project](https://github.com/jupyter/docker-stacks) and on [Docker Hub](https://hub.docker.com/u/jupyter/). 8 | 9 | The images that the Jupyter project provides will not work with the default security profile of OpenShift. This is because the Jupyter project images, although they have attempted to set them up so they do not run as ``root``, will not run in a multi tenant PaaS environment where any container a user runs is forced to run with an assigned ``uid`` different to that specified by the image. 10 | 11 | The issues preventing the Jupyter project images being able to be run in a default installation of OpenShift have been [reported](https://github.com/jupyter/docker-stacks/issues/188) via the GitHub project but at this point have not been addressed. 12 | 13 | Fixed up versions of the Jupyter project images that can be run on OpenShift can be found in another ``getwarped`` project described at: 14 | 15 | * [https://github.com/getwarped/jupyter-stacks](https://github.com/getwarped/jupyter-stacks) 16 | 17 | The images based on the Jupyter project images, provide various stacks with a range of different packages pre-installed, including both Python 2 and Python 3 runtimes in some images. Those images do have basic S2I support for when using Python as well, but the primary intention is to make available an OpenShift compatible version of the Jupyter project images for use in ad-hoc situations. 18 | 19 | In contrast, the images provided here are setup for only a single Python version and only provide a minimal notebook configuration. The intent is that the images here be used as S2I builders to build up purpose built images which include only those packages that you need. This provides a smaller and more reproducible image as it requires that all the dependencies you need are listed. 20 | 21 | The images here also include built-in support for being used as part of a parallel computing cluster using ``ipyparallel``. The Jupyter project images cannot be used in the same way and would need additional work to set them up to be used with ``ipyparallel``. 22 | 23 | So if you want an ad-hoc environment to play in, the Jupyter project images may be more suitable, but where you need your environment to be properly specified so you know what is being included, or need to use ``ipyparallel``, the images here would be a better option. 24 | 25 | ## Jupyter Notebook Base Image 26 | 27 | Two Jupyter notebook base images are currently being made available: 28 | 29 | * getwarped/s2i-notebook-python27 - ([GitHub Repository](https://github.com/getwarped/s2i-notebook-python27), [Docker Hub](https://hub.docker.com/r/getwarped/s2i-notebook-python27/)) 30 | * getwarped/s2i-notebook-python35 - ([GitHub Repository](https://github.com/getwarped/s2i-notebook-python35), [Docker Hub](https://hub.docker.com/r/getwarped/s2i-notebook-python35/)) 31 | 32 | The images include only the basic Python packages required to run a Jupyter notebook server, as well as the ``ipyparallel`` package for parallel computing. 33 | 34 | The images are Source-to-Image (S2I) enabled, and use ``waprdrive`` to make it easy to build up custom images including additional Python packages or code, and sample notebook files and data. 35 | 36 | ## Loading Images and Templates 37 | 38 | The easiest way to deploy the Jupyter notebook images and get working is to import which of the above images you wish to use and then use the OpenShift templates provided with this project to deploy them. 39 | 40 | To import all the above images into your project, you can run the ``oc import-image`` command. 41 | 42 | ``` 43 | oc import-image getwarped/s2i-notebook-python27 --confirm 44 | oc import-image getwarped/s2i-notebook-python35 --confirm 45 | ``` 46 | 47 | This should result in the creation of two image streams within your project. 48 | 49 | ``` 50 | s2i-notebook-python27 51 | s2i-notebook-python35 52 | ``` 53 | 54 | You could deploy the images directly, but the templates provide an easier way of setting a password for your notebooks, and will also ensure that a secure HTTP connection is used when interacting with the notebook interface. 55 | 56 | To load the OpenShift templates you can run the ``oc create`` command. 57 | 58 | 59 | ``` 60 | oc create -f https://raw.githubusercontent.com/getwarped/jupyter-notebooks/master/openshift/templates.json 61 | ``` 62 | 63 | This should create three templates within your project. The purpose of each template is as follows: 64 | 65 | * ``jupyter-notebook`` - Deploy a notebook server from an image stream. This can be one of the basic images listed above, or a customised image which has been created using the ``jupyter-builder`` template. The notebook can optionally be linked to a parallel compute cluster created using ``jupyter-cluster``. 66 | 67 | * ``jupyter-builder`` - Create a customised notebook image. This will run the S2I build process, starting with any of the basic images, or even a customised image, to bind additional files into the image. This can be used to incorporate pre-defined notebooks, data files, or install additional Python packages. 68 | 69 | * ``jupyter-cluster`` - Deploy a parallel compute cluster comprising of a controller and single compute engine. The number of compute engines can be scaled up to as many as necessary. 70 | 71 | ## Deploying the Notebook Server 72 | 73 | To deploy a notebook server, select _Add to Project_ from the web console and enter ``jupyter`` into the search filter. Select ``jupyter-notebook``. 74 | 75 | ![image](images/add_to_project_jupyter_notebook.png) 76 | 77 | On the page for the ``jupyter-notebook`` template fill in the name of the application, the name of the image you wish to deploy (defaults to ``s2i-notebook-python27``) and a password for the notebook server. Also override the ``app`` label applied to resources created when deploying the notebook server with a unique name. This will make it easier to delete the notebook server later. 78 | 79 | ![image](images/create_jupyter_notebook.png) 80 | 81 | If you do not provide your own password, a random password will be used. You can find out the name of the generated password by going to the _Environment_ tab of the _Deployments_ page for your notebook server in the OpenShift web console. 82 | 83 | Once created the notebook server will be deployed and automatically exposed via a route to the Internet over a secure HTTP connection. You can find the URL it is exposed as from the _Overview_ page for your project. 84 | 85 | ![image](images/overview_notebook.png) 86 | 87 | When you visit the URL you will be prompted for the password you entered via the template to get access to the notebook server. 88 | 89 | If you use either of the ``s2i-notebook-python27`` or ``s2i-notebook-python35`` images in the ``NOTEBOOK_IMAGE`` field of the template, you will be presented with an empty work directory. You can create new notebooks or upload your own as necessary. 90 | 91 | Do note that by default any work you do is not being saved in a persistent volume. Thus if the notebook server is restarted at any point by you explicitly, or by OpenShift, your work will be lost. 92 | 93 | To enable you to preserve your work, even if the notebook server is restarted, you should attach a persistent storage volume to the notebook server. This can be done from the _Deployments_ page for your notebook server. 94 | 95 | ![image](images/deployments_attach_storage_notebook.png) 96 | 97 | The mount path for the storage should be set to be ``/opt/app-root/src``. 98 | 99 | ![image](images/attach_storage_notebook.png) 100 | 101 | The storage should be attached before you do anything else. The notebook server will be automatically restarted when you make the storage request and attach the volume. 102 | 103 | When done with your work, you can download your files using the Jupyter notebook interface. Alternatively, you can use the ``oc rsync`` command to copy files from your application back to your local computer. 104 | 105 | To delete the Jupyter notebook server when no longer required, you can use the ``oc delete all --selector app=`` command, where ```` is replaced with the value you gave the ``app`` label in the template page when deploying the Jupyter notebook server. 106 | 107 | Note that when the application is deleted, the persistent storage volume will not be deleted. To delete that you should determine the name of the persistent storage volume using ``oc get pvc`` and then delete it using ``oc delete pvc/``. 108 | 109 | ## Creating a Custom Image 110 | 111 | Only a basic Jupyter notebook image is provided. If your Jupyter notebooks require additional Python packages you can create a customised image using the ``oc new-build`` command, or the ``jupyter-builder`` template. 112 | 113 | From the web console, select ``jupyter-builder`` from the _Add to Project_ page. You will be asked to enter in the name to give the custom notebook image created, the name of the S2I builder image to use as the base, and the details of a source code repository from which files will be pulled to incorporate into the custom image. To have additional Python packages installed into the custom image, you should add a ``requirements.txt`` for ``pip`` into the directory of the source code repository that files are pulled from. 114 | 115 | ![image](images/create_jupyter_builder.png) 116 | 117 | This template will only create a build and it will not appear on the _Overview_ page. You will be able to find it under the _Builds_ page. 118 | 119 | When the build is complete, you can then use the ``jupyter-notebook`` template to run the image, as described above, by setting ``NOTEBOOK_IMAGE`` to be the name of your custom image. 120 | 121 | ## Running a Compute Cluster 122 | 123 | Support for running a parallel computing cluster using ``ipyparallel`` has been incorporated into all the S2I enabled images. 124 | 125 | To set up your own compute cluster, you can use the ``jupyter-cluster`` template. Give your cluster a name and specify the notebook image to use when starting the controller and compute engines. 126 | 127 | ![image](images/create_jupyter_cluster.png) 128 | 129 | If you need additional Python packages, data files or Python code available in the compute engines, you can build it into a custom image to be used when starting up the cluster using the ``jupyter-builder`` template. 130 | 131 | Once the cluster is running, you can increase the number of compute engines by increasing the replica count on the ``ipengine`` component. 132 | 133 | ![image](images/scale_up_compute_engine.png) 134 | 135 | To link the cluster with a Jupyter notebook server, specify the name of the cluster when deploying the notebook server using the ``jupyter-notebook`` template. 136 | 137 | ![image](images/attach_compute_cluster.png) 138 | 139 | The cluster will be associated with the default profile, so the ``ipyparallel`` client can be used without needing any special arguments when it is initialised. 140 | 141 | ![image](images/ipyparallel_client.png) 142 | -------------------------------------------------------------------------------- /images/add_to_project_jupyter_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/add_to_project_jupyter_notebook.png -------------------------------------------------------------------------------- /images/attach_compute_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/attach_compute_cluster.png -------------------------------------------------------------------------------- /images/attach_storage_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/attach_storage_notebook.png -------------------------------------------------------------------------------- /images/create_jupyter_builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/create_jupyter_builder.png -------------------------------------------------------------------------------- /images/create_jupyter_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/create_jupyter_cluster.png -------------------------------------------------------------------------------- /images/create_jupyter_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/create_jupyter_notebook.png -------------------------------------------------------------------------------- /images/deployments_attach_storage_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/deployments_attach_storage_notebook.png -------------------------------------------------------------------------------- /images/ipyparallel_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/ipyparallel_client.png -------------------------------------------------------------------------------- /images/overview_notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/overview_notebook.png -------------------------------------------------------------------------------- /images/scale_up_compute_engine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamDumpleton-abandoned/jupyter-notebooks/5dd5250583101cd13551c4a5f82bd257e5247fc2/images/scale_up_compute_engine.png -------------------------------------------------------------------------------- /notebooks/ipyparallel-testing/cluster-test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "%matplotlib inline\n", 12 | "import matplotlib.pyplot as plt" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 2, 18 | "metadata": { 19 | "collapsed": true 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "import os\n", 24 | "import time\n", 25 | "import numpy" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 3, 31 | "metadata": { 32 | "collapsed": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from ipyparallel import Client" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 4, 42 | "metadata": { 43 | "collapsed": true 44 | }, 45 | "outputs": [], 46 | "source": [ 47 | "cli = Client()" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 5, 53 | "metadata": { 54 | "collapsed": false 55 | }, 56 | "outputs": [ 57 | { 58 | "data": { 59 | "text/plain": [ 60 | "[0, 1, 2, 3]" 61 | ] 62 | }, 63 | "execution_count": 5, 64 | "metadata": {}, 65 | "output_type": "execute_result" 66 | } 67 | ], 68 | "source": [ 69 | "cli.ids" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 6, 75 | "metadata": { 76 | "collapsed": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "def hostname():\n", 81 | " import os\n", 82 | " return os.environ['HOSTNAME']" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 7, 88 | "metadata": { 89 | "collapsed": false 90 | }, 91 | "outputs": [ 92 | { 93 | "data": { 94 | "text/plain": [ 95 | "'nbviewer-228ba-46ae3-1-xk8z3'" 96 | ] 97 | }, 98 | "execution_count": 7, 99 | "metadata": {}, 100 | "output_type": "execute_result" 101 | } 102 | ], 103 | "source": [ 104 | "hostname()" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 8, 110 | "metadata": { 111 | "collapsed": false 112 | }, 113 | "outputs": [ 114 | { 115 | "data": { 116 | "text/plain": [ 117 | "['ipengine-46ae3-1-sqmpi',\n", 118 | " 'ipengine-46ae3-1-omi0x',\n", 119 | " 'ipengine-46ae3-1-rbtx3',\n", 120 | " 'ipengine-46ae3-1-gs6z3']" 121 | ] 122 | }, 123 | "execution_count": 8, 124 | "metadata": {}, 125 | "output_type": "execute_result" 126 | } 127 | ], 128 | "source": [ 129 | "cli[:].apply_sync(hostname)" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 9, 135 | "metadata": { 136 | "collapsed": true 137 | }, 138 | "outputs": [], 139 | "source": [ 140 | "dview = cli[:]" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 10, 146 | "metadata": { 147 | "collapsed": true 148 | }, 149 | "outputs": [], 150 | "source": [ 151 | "@dview.parallel(block=True)\n", 152 | "def dummy_task(delay):\n", 153 | " \"\"\" a dummy task that takes 'delay' seconds to finish \"\"\"\n", 154 | " import os, time\n", 155 | "\n", 156 | " t0 = time.time()\n", 157 | " hostname = os.environ['HOSTNAME']\n", 158 | " time.sleep(delay)\n", 159 | " t1 = time.time()\n", 160 | " \n", 161 | " return [hostname, t0, t1]" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 11, 167 | "metadata": { 168 | "collapsed": true 169 | }, 170 | "outputs": [], 171 | "source": [ 172 | "delay_times = numpy.random.rand((len(cli.ids)))" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 12, 178 | "metadata": { 179 | "collapsed": false 180 | }, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/plain": [ 185 | "[['ipengine-46ae3-1-sqmpi', 1450325484.694289, 1450325484.714024],\n", 186 | " ['ipengine-46ae3-1-omi0x', 1450325484.960086, 1450325485.195587],\n", 187 | " ['ipengine-46ae3-1-rbtx3', 1450325484.9646, 1450325485.325554],\n", 188 | " ['ipengine-46ae3-1-gs6z3', 1450325484.965222, 1450325485.574295]]" 189 | ] 190 | }, 191 | "execution_count": 12, 192 | "metadata": {}, 193 | "output_type": "execute_result" 194 | } 195 | ], 196 | "source": [ 197 | "dummy_task.map(delay_times)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 13, 203 | "metadata": { 204 | "collapsed": true 205 | }, 206 | "outputs": [], 207 | "source": [ 208 | "def visualize_tasks(results):\n", 209 | " res = numpy.array(results)\n", 210 | " fig, ax = plt.subplots(figsize=(10, res.shape[1]))\n", 211 | " \n", 212 | " yticks = []\n", 213 | " yticklabels = []\n", 214 | " tmin = float(min(res[:,1]))\n", 215 | " print(tmin)\n", 216 | " for n, hostname in enumerate(numpy.unique(res[:,0])):\n", 217 | " yticks.append(n)\n", 218 | " yticklabels.append(\"%s\" % hostname)\n", 219 | " for m in numpy.where(res[:,0] == hostname)[0]:\n", 220 | " pass\n", 221 | " ax.add_patch(plt.Rectangle((float(res[m,1]) - tmin, n-0.25),\n", 222 | " float(res[m,2]) - float(res[m,1]), 0.5, color=\"green\", alpha=0.5))\n", 223 | " \n", 224 | " ax.set_ylim(-.5, n+.5)\n", 225 | " ax.set_xlim(0, float(max(res[:,2])) - tmin + 0.)\n", 226 | " ax.set_yticks(yticks)\n", 227 | " ax.set_yticklabels(yticklabels)\n", 228 | " ax.set_ylabel(\"PID\")\n", 229 | " ax.set_xlabel(\"seconds\")" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 14, 235 | "metadata": { 236 | "collapsed": true 237 | }, 238 | "outputs": [], 239 | "source": [ 240 | "delay_times = numpy.random.rand(64)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 15, 246 | "metadata": { 247 | "collapsed": false 248 | }, 249 | "outputs": [ 250 | { 251 | "name": "stdout", 252 | "output_type": "stream", 253 | "text": [ 254 | "1450325493.15\n" 255 | ] 256 | }, 257 | { 258 | "data": { 259 | "image/png": "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\nM19EREREs+YFvBvOa71G2e7Si8bEbf9SLzsFOBpYAtwCHChpNwBJW0vao+Y8jZaW45F0KLB9i/Hq\n/BDYabB+WNI0SXvbvt/2frbn2F5k+2O2Z9qeRVVGcV1JkqFa5R6s2d0T2ML2w8AM4IHS5zhgatm+\nGniPpG3KMTtL2qnUWs8obVsBC4BVzbE0ze2Z+dX0+0/gTZKeX8Z6S2kbyXVpdgVVGQfA24HrBq8X\ncHa5JvdI+vAmnjciIiKiZ7R7RblxFXkFcDqwO1XieTmApOOBiyRtWfp/HPgRz12BHnQycKGkdwI3\nU9X3PlYz3nPisP2UpLcBp5UkdSrVUx02pTb4XOAcSaupVmYHE+gzgMskHQdcBTxexrxG0iuAm6tq\nDx4D3glsC5zXUAJyge1r6waUdCHQD+woaR2w0Pa5z5qgfZukrwDLy3wX2b5d0q5N16XVNWp0NnCB\npB8BD1MlxgAfBW60fZOqR9Utk/Rt2z8cwTkjIiIiekpbE2Xb0xverrd9ZE2fAWD/mvZZDdsrKau4\nVHXGh9neWFaG59l+qnG8Uud7Q8PxJzRs3wEctAlzaD7XU8C7avrdC7yqoemjDftOo9zc12TOCGM4\ndoT9vkCV+De23Uf1+LnB9++p22f7PMqTLsqj6/6k5vz/2LD9a6r68oiIiIgJqZM1ymNlJnBxKeHY\nALy3y/FERERExATUkUS5eVV2M891LyNciY2IiIiIGK1238wXEREREdGTkihHRERERNRIohwRERER\nUaMXb+aLHjBzxkzW/mptV2PYebudux5DnbGMa7zOsZVOxbup4/TadYTejHlTDTfHXrkGvRLnptrc\neXXjuvTiZ9GLMQ+l1+YjeySP1Y0YOUnOz1VERET0AknYrv1ytpReRERERETUSKIcEREREVEjiXJE\nRERERI0kyhERERERNZIoR0RERETUSKIcEREREVEjiXJERERERI0kyhERERERNZIoR0RERETUSKIc\nEREREVEjiXJERERERI0kyhERERERNZIoR0RERETUSKIcEREREVEjiXJERERERI0kyhERERERNZIo\nR0RERETUSKIcEREREVEjiXJERERERI1p3Q4gJqZPXP8J1q1f1+0wnrHmkTXM2mFWz55/c4zn2EZr\nPH+erY7t5OcwET/zofTyfIeKfSzndesDtzLnJXPG5FzjyXj57MdLHGNt5oyZnHLwKd0Oo6uSKEdb\n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\nqdkH+/+HAAAAAElFTkSuQmCC\n", 260 | "text/plain": [ 261 | "" 262 | ] 263 | }, 264 | "metadata": {}, 265 | "output_type": "display_data" 266 | } 267 | ], 268 | "source": [ 269 | "result = dummy_task.map(delay_times)\n", 270 | "visualize_tasks(result)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 16, 276 | "metadata": { 277 | "collapsed": true 278 | }, 279 | "outputs": [], 280 | "source": [ 281 | "lbview = cli.load_balanced_view()" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": 17, 287 | "metadata": { 288 | "collapsed": true 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "@lbview.parallel(block=True)\n", 293 | "def dummy_task_load_balanced(delay):\n", 294 | " \"\"\" a dummy task that takes 'delay' seconds to finish \"\"\"\n", 295 | " import os, time\n", 296 | "\n", 297 | " t0 = time.time()\n", 298 | " hostname = os.environ['HOSTNAME']\n", 299 | " time.sleep(delay)\n", 300 | " t1 = time.time()\n", 301 | " \n", 302 | " return [hostname, t0, t1]" 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "execution_count": 18, 308 | "metadata": { 309 | "collapsed": false 310 | }, 311 | "outputs": [ 312 | { 313 | "name": "stdout", 314 | "output_type": "stream", 315 | "text": [ 316 | "1450325512.52\n" 317 | ] 318 | }, 319 | { 320 | "data": { 321 | "image/png": "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\nR0RERIwJnXh7uIkN3661fXhNnwFgv5r2qQ3byyiruFR1xofYXl9WhmfZfrJxvFLne13D8cc2bK8A\n5mzCNTSf60ng/TX97gZe29D0qYZ9J1Me7msyY5gxHDWMPguABS3aXw3Vu4EAnyjtJzT1m9iw/ax9\nwBdtf6qpf3OfiIiIiDGj0zXKI6UPuLCUcKzj2Q+nxcjbaE1yRERExFjTsUS5eVX2eZ7rboa5EhvP\ntjmvQ+PKfkRERMSWohMP80VERERE9JwkyhERERERNZIoR0RERETUSKIcEREREVGjV9/1Ika5vkl9\nrH54dbfD2GDyDpNHPJ6RPmfz+doRcy/qm9QH0NG5GGruuxHP89XL99Jojn00xzZSRvM1jubYBvXi\n74s6vTDX7SI77/wVI0uSc19FREREL5CE7dpPJ07pRUREREREjSTKERERERE1kihHRERERNRIohwR\nERERUSOJckREREREjSTKERERERE1kihHRERERNRIohwRERERUSOJckREREREjSTKERERERE1kihH\nRERERNRIohwRERERUSOJckREREREjSTKERERERE1kihHRERERNRIohwRERERUSOJckREREREjSTK\nERERERE1kihHRERERNSY0O0AYmz67LWfZc3aNQCsemgVU3ea2uWIOqtvUh/AhjnYErTjmnv13unV\nuDfVaL7OkYqtE9c4mudxJI2VuRzqd92W8lrW6ZvUx4kHntjtMEZcEuVoizVr1zBlxykALFyzkINe\neVB3A+qw1Q+vBtgwB1uCdlxzr947vRr3phrN1zlSsXXiGkfzPI6ksTKXQ/2u21JeyzqD8zLWpPQi\nIiIiIqJGEuWIiIiIiBpJlCMiIiIiaiRRjoiIiIio0dFEWdLCXh5P0jhJN0u6rKn9Y5LulLRS0kmb\nee6tJf1Y0i2Sbpf0+SH6niHpAUkrNnGMR1u0f0DSS4dx/I9KfLdJ+oakPAwaERERY1ZHE2Xbs3t8\nvI8DdzQ2SOoH3grsbXtv4Eubc2Lb64ADbe8L7AMcJOmAFt3PAv54U84vSYBb7D4GePkwTvMe2/va\nfg2wI3DEpsQQERER0Us6vaL8aPk6R9J1kr4v6S5Jpzb0mSvpBklLJX1H0ral/eeSPidpmaRbJU0r\n7S+WtKCs5p4uabWkF9WMd62ki8rK73kN482QNCBpSVkxfUmL2HcFDgW+0bTrw8BJtp8CsP2b0n83\n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\nAAAASUVORK5CYII=\n", 322 | "text/plain": [ 323 | "" 324 | ] 325 | }, 326 | "metadata": {}, 327 | "output_type": "display_data" 328 | } 329 | ], 330 | "source": [ 331 | "result = dummy_task_load_balanced.map(delay_times)\n", 332 | "visualize_tasks(result)" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "metadata": { 339 | "collapsed": true 340 | }, 341 | "outputs": [], 342 | "source": [] 343 | } 344 | ], 345 | "metadata": { 346 | "kernelspec": { 347 | "display_name": "Python 2", 348 | "language": "python", 349 | "name": "python2" 350 | }, 351 | "language_info": { 352 | "codemirror_mode": { 353 | "name": "ipython", 354 | "version": 2 355 | }, 356 | "file_extension": ".py", 357 | "mimetype": "text/x-python", 358 | "name": "python", 359 | "nbconvert_exporter": "python", 360 | "pygments_lexer": "ipython2", 361 | "version": "2.7.11" 362 | } 363 | }, 364 | "nbformat": 4, 365 | "nbformat_minor": 0 366 | } 367 | -------------------------------------------------------------------------------- /notebooks/ipyparallel-testing/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | numpy 3 | -------------------------------------------------------------------------------- /openshift/images.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "ImageStream", 4 | "metadata": { 5 | "annotations": { 6 | "openshift.io/display-name": "Jupyter Notebook" 7 | }, 8 | "name": "jupyter-notebook" 9 | }, 10 | "spec": { 11 | "tags": [ 12 | { 13 | "annotations": { 14 | "description": "Build and deploy custom Jupyter Notebook images for Python 2.7.", 15 | "iconClass": "icon-python", 16 | "openshift.io/display-name": "Jupyter Notebook (Python 2.7)", 17 | "sampleRepo": "https://github.com/ricardoduarte/python-for-developers.git", 18 | "supports": "python", 19 | "tags": "builder,python,jupyter", 20 | "version": "2.7" 21 | }, 22 | "from": { 23 | "apiVersion": "v1", 24 | "kind": "DockerImage", 25 | "name": "getwarped/s2i-notebook-python27:latest" 26 | }, 27 | "name": "2.7" 28 | }, 29 | { 30 | "annotations": { 31 | "description": "Build and deploy custom Jupyter Notebook images for Python 3.5.", 32 | "iconClass": "icon-python", 33 | "openshift.io/display-name": "Jupyter Notebook (Python 3.5)", 34 | "sampleRepo": "https://github.com/ricardoduarte/python-for-developers.git", 35 | "supports": "python", 36 | "tags": "builder,python,jupyter", 37 | "version": "3.5" 38 | }, 39 | "from": { 40 | "apiVersion": "v1", 41 | "kind": "DockerImage", 42 | "name": "getwarped/s2i-notebook-python35:latest" 43 | }, 44 | "name": "3.5" 45 | }, 46 | { 47 | "annotations": { 48 | "description": "Build and deploy custom Jupyter Notebook images for Python 3.X.", 49 | "iconClass": "icon-python", 50 | "openshift.io/display-name": "Jupyter Notebook (Python 3.X)", 51 | "sampleRepo": "https://github.com/ricardoduarte/python-for-developers.git", 52 | "supports": "python", 53 | "tags": "builder,python,jupyter" 54 | }, 55 | "from": { 56 | "apiVersion": "v1", 57 | "kind": "ImageStreamTag", 58 | "name": "3.5" 59 | }, 60 | "name": "latest" 61 | } 62 | ] 63 | } 64 | } -------------------------------------------------------------------------------- /openshift/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "List", 3 | "apiVersion": "v1", 4 | "metadata": {}, 5 | "items": [ 6 | { 7 | "kind": "Template", 8 | "apiVersion": "v1", 9 | "metadata": { 10 | "name": "jupyter-notebook", 11 | "annotations": { 12 | "description": "Jupyter Notebook", 13 | "tags": "instant-app,jupyter,notebook" 14 | } 15 | }, 16 | "parameters": [ 17 | { 18 | "name": "APPLICATION_NAME", 19 | "description": "The name of the notebook application.", 20 | "value": "notebook", 21 | "from": "[a-zA-Z0-9]", 22 | "required": true 23 | }, 24 | { 25 | "name": "BUILDER_IMAGE", 26 | "description": "The name of the builder image.", 27 | "value": "jupyter-notebook:3.5", 28 | "from": "[a-zA-Z0-9-]", 29 | "required": true 30 | }, 31 | { 32 | "name": "SOURCE_REPOSITORY", 33 | "description": "Git repository for source files.", 34 | "value": "", 35 | "required": true 36 | }, 37 | { 38 | "name": "SOURCE_DIRECTORY", 39 | "description": "Sub-directory of Git repository.", 40 | "value": "", 41 | "required": false 42 | }, 43 | { 44 | "description": "Password for the notebook application.", 45 | "name": "NOTEBOOK_PASSWORD", 46 | "from": "[a-f0-9]{8}", 47 | "generate": "expression" 48 | }, 49 | { 50 | "name": "CLUSTER_NAME", 51 | "description": "The name of the notebook cluster.", 52 | "value": "", 53 | "from": "[a-zA-Z0-9-]" 54 | } 55 | ], 56 | "objects": [ 57 | { 58 | "kind": "ImageStream", 59 | "apiVersion": "v1", 60 | "metadata": { 61 | "name": "${APPLICATION_NAME}", 62 | "labels": { 63 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 64 | } 65 | } 66 | }, 67 | { 68 | "kind": "BuildConfig", 69 | "apiVersion": "v1", 70 | "metadata": { 71 | "name": "${APPLICATION_NAME}", 72 | "labels": { 73 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 74 | } 75 | }, 76 | "spec": { 77 | "triggers": [ 78 | { 79 | "type": "ConfigChange" 80 | }, 81 | { 82 | "type": "ImageChange" 83 | } 84 | ], 85 | "source": { 86 | "type": "Git", 87 | "git": { 88 | "uri": "${SOURCE_REPOSITORY}" 89 | }, 90 | "contextDir": "${SOURCE_DIRECTORY}" 91 | }, 92 | "strategy": { 93 | "type": "Source", 94 | "sourceStrategy": { 95 | "from": { 96 | "kind": "ImageStreamTag", 97 | "name": "${BUILDER_IMAGE}" 98 | } 99 | } 100 | }, 101 | "output": { 102 | "to": { 103 | "kind": "ImageStreamTag", 104 | "name": "${APPLICATION_NAME}:latest" 105 | } 106 | } 107 | } 108 | }, 109 | { 110 | "kind": "DeploymentConfig", 111 | "apiVersion": "v1", 112 | "metadata": { 113 | "name": "${APPLICATION_NAME}", 114 | "labels": { 115 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 116 | } 117 | }, 118 | "spec": { 119 | "triggers": [ 120 | { 121 | "type": "ConfigChange" 122 | }, 123 | { 124 | "type": "ImageChange", 125 | "imageChangeParams": { 126 | "automatic": true, 127 | "containerNames": [ 128 | "${APPLICATION_NAME}" 129 | ], 130 | "from": { 131 | "kind": "ImageStreamTag", 132 | "name": "${APPLICATION_NAME}:latest" 133 | } 134 | } 135 | } 136 | ], 137 | "replicas": 1, 138 | "selector": { 139 | "deploymentconfig": "${APPLICATION_NAME}" 140 | }, 141 | "template": { 142 | "metadata": { 143 | "labels": { 144 | "appid": "jupyter-notebook-${APPLICATION_NAME}", 145 | "deploymentconfig": "${APPLICATION_NAME}" 146 | } 147 | }, 148 | "spec": { 149 | "containers": [ 150 | { 151 | "name": "${APPLICATION_NAME}", 152 | "image": "${APPLICATION_NAME}:latest", 153 | "ports": [ 154 | { 155 | "containerPort": 8080, 156 | "protocol": "TCP" 157 | } 158 | ], 159 | "env": [ 160 | { 161 | "name": "JUPYTER_NOTEBOOK_PASSWORD", 162 | "value": "${NOTEBOOK_PASSWORD}" 163 | }, 164 | { 165 | "name": "IPYPARALLEL_CONTROLLER_NAME", 166 | "value": "ipcontroller-${CLUSTER_NAME}" 167 | } 168 | ] 169 | } 170 | ] 171 | } 172 | } 173 | } 174 | }, 175 | { 176 | "kind": "Service", 177 | "apiVersion": "v1", 178 | "metadata": { 179 | "name": "${APPLICATION_NAME}", 180 | "labels": { 181 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 182 | } 183 | }, 184 | "spec": { 185 | "ports": [ 186 | { 187 | "name": "8080-tcp", 188 | "protocol": "TCP", 189 | "port": 8080, 190 | "targetPort": 8080 191 | } 192 | ], 193 | "selector": { 194 | "deploymentconfig": "${APPLICATION_NAME}" 195 | } 196 | } 197 | }, 198 | { 199 | "kind": "Route", 200 | "apiVersion": "v1", 201 | "metadata": { 202 | "name": "${APPLICATION_NAME}", 203 | "labels": { 204 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 205 | } 206 | }, 207 | "spec": { 208 | "to": { 209 | "kind": "Service", 210 | "name": "${APPLICATION_NAME}" 211 | }, 212 | "port": { 213 | "targetPort": "8080-tcp" 214 | }, 215 | "tls": { 216 | "termination": "edge" 217 | } 218 | } 219 | } 220 | ] 221 | }, 222 | { 223 | "kind": "Template", 224 | "apiVersion": "v1", 225 | "metadata": { 226 | "name": "jupyter-deployer", 227 | "annotations": { 228 | "description": "Jupyter Notebook Deployer", 229 | "tags": "instant-app,jupyter,notebook" 230 | } 231 | }, 232 | "parameters": [ 233 | { 234 | "name": "APPLICATION_NAME", 235 | "description": "The name of the notebook application.", 236 | "value": "notebook", 237 | "from": "[a-zA-Z0-9]", 238 | "required": true 239 | }, 240 | { 241 | "name": "NOTEBOOK_IMAGE", 242 | "description": "The name of the notebook image.", 243 | "value": "jupyter-notebook:3.5", 244 | "required": true 245 | }, 246 | { 247 | "description": "Password for the notebook application.", 248 | "name": "NOTEBOOK_PASSWORD", 249 | "from": "[a-f0-9]{8}", 250 | "generate": "expression" 251 | }, 252 | { 253 | "name": "CLUSTER_NAME", 254 | "description": "The name of the notebook cluster.", 255 | "value": "", 256 | "from": "[a-zA-Z0-9-]" 257 | } 258 | ], 259 | "objects": [ 260 | { 261 | "kind": "DeploymentConfig", 262 | "apiVersion": "v1", 263 | "metadata": { 264 | "name": "${APPLICATION_NAME}", 265 | "labels": { 266 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 267 | } 268 | }, 269 | "spec": { 270 | "triggers": [ 271 | { 272 | "type": "ConfigChange" 273 | }, 274 | { 275 | "type": "ImageChange", 276 | "imageChangeParams": { 277 | "automatic": true, 278 | "containerNames": [ 279 | "${APPLICATION_NAME}" 280 | ], 281 | "from": { 282 | "kind": "ImageStreamTag", 283 | "name": "${NOTEBOOK_IMAGE}" 284 | } 285 | } 286 | } 287 | ], 288 | "replicas": 1, 289 | "selector": { 290 | "deploymentconfig": "${APPLICATION_NAME}" 291 | }, 292 | "template": { 293 | "metadata": { 294 | "labels": { 295 | "appid": "jupyter-notebook-${APPLICATION_NAME}", 296 | "deploymentconfig": "${APPLICATION_NAME}" 297 | } 298 | }, 299 | "spec": { 300 | "containers": [ 301 | { 302 | "name": "${APPLICATION_NAME}", 303 | "image": "${NOTEBOOK_IMAGE}", 304 | "ports": [ 305 | { 306 | "containerPort": 8080, 307 | "protocol": "TCP" 308 | } 309 | ], 310 | "env": [ 311 | { 312 | "name": "JUPYTER_NOTEBOOK_PASSWORD", 313 | "value": "${NOTEBOOK_PASSWORD}" 314 | }, 315 | { 316 | "name": "IPYPARALLEL_CONTROLLER_NAME", 317 | "value": "ipcontroller-${CLUSTER_NAME}" 318 | } 319 | ] 320 | } 321 | ] 322 | } 323 | } 324 | } 325 | }, 326 | { 327 | "kind": "Service", 328 | "apiVersion": "v1", 329 | "metadata": { 330 | "name": "${APPLICATION_NAME}", 331 | "labels": { 332 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 333 | } 334 | }, 335 | "spec": { 336 | "ports": [ 337 | { 338 | "name": "8080-tcp", 339 | "protocol": "TCP", 340 | "port": 8080, 341 | "targetPort": 8080 342 | } 343 | ], 344 | "selector": { 345 | "deploymentconfig": "${APPLICATION_NAME}" 346 | } 347 | } 348 | }, 349 | { 350 | "kind": "Route", 351 | "apiVersion": "v1", 352 | "metadata": { 353 | "name": "${APPLICATION_NAME}", 354 | "labels": { 355 | "appid": "jupyter-notebook-${APPLICATION_NAME}" 356 | } 357 | }, 358 | "spec": { 359 | "to": { 360 | "kind": "Service", 361 | "name": "${APPLICATION_NAME}" 362 | }, 363 | "port": { 364 | "targetPort": "8080-tcp" 365 | }, 366 | "tls": { 367 | "termination": "edge" 368 | } 369 | } 370 | } 371 | ] 372 | }, 373 | { 374 | "kind": "Template", 375 | "apiVersion": "v1", 376 | "metadata": { 377 | "name": "jupyter-builder", 378 | "annotations": { 379 | "description": "Jupyter Notebook Builder", 380 | "tags": "builder,jupyter,notebook" 381 | } 382 | }, 383 | "parameters": [ 384 | { 385 | "description": "The name of the notebook image.", 386 | "name": "NOTEBOOK_IMAGE", 387 | "from": "[a-aA-Z0-9-]", 388 | "required": true 389 | }, 390 | { 391 | "name": "BUILDER_IMAGE", 392 | "description": "The name of the builder image.", 393 | "value": "jupyter-notebook:3.5", 394 | "from": "[a-zA-Z0-9-]", 395 | "required": true 396 | }, 397 | { 398 | "name": "SOURCE_REPOSITORY", 399 | "description": "Git repository for source files.", 400 | "value": "", 401 | "required": true 402 | }, 403 | { 404 | "name": "SOURCE_DIRECTORY", 405 | "description": "Sub-directory of Git repository.", 406 | "value": "", 407 | "required": false 408 | } 409 | ], 410 | "objects": [ 411 | { 412 | "kind": "ImageStream", 413 | "apiVersion": "v1", 414 | "metadata": { 415 | "name": "${NOTEBOOK_IMAGE}", 416 | "labels": { 417 | "appid": "jupyter-builder-${NOTEBOOK_IMAGE}" 418 | } 419 | } 420 | }, 421 | { 422 | "kind": "BuildConfig", 423 | "apiVersion": "v1", 424 | "metadata": { 425 | "name": "${NOTEBOOK_IMAGE}", 426 | "labels": { 427 | "appid": "jupyter-builder-${NOTEBOOK_IMAGE}" 428 | } 429 | }, 430 | "spec": { 431 | "triggers": [ 432 | { 433 | "type": "ConfigChange" 434 | }, 435 | { 436 | "type": "ImageChange" 437 | } 438 | ], 439 | "source": { 440 | "type": "Git", 441 | "git": { 442 | "uri": "${SOURCE_REPOSITORY}" 443 | }, 444 | "contextDir": "${SOURCE_DIRECTORY}" 445 | }, 446 | "strategy": { 447 | "type": "Source", 448 | "sourceStrategy": { 449 | "from": { 450 | "kind": "ImageStreamTag", 451 | "name": "${BUILDER_IMAGE}" 452 | } 453 | } 454 | }, 455 | "output": { 456 | "to": { 457 | "kind": "ImageStreamTag", 458 | "name": "${NOTEBOOK_IMAGE}:latest" 459 | } 460 | } 461 | } 462 | } 463 | ] 464 | }, 465 | { 466 | "kind": "Template", 467 | "apiVersion": "v1", 468 | "metadata": { 469 | "name": "jupyter-cluster", 470 | "annotations": { 471 | "description": "Jupyter Notebook Cluster", 472 | "tags": "instant-app,jupyter,cluster" 473 | } 474 | }, 475 | "parameters": [ 476 | { 477 | "description": "The name of the notebook cluster.", 478 | "name": "CLUSTER_NAME", 479 | "from": "[a-zA-Z0-9-]", 480 | "required": true 481 | }, 482 | { 483 | "description": "The name of the notebook image.", 484 | "name": "NOTEBOOK_IMAGE", 485 | "value": "jupyter-notebook:3.5", 486 | "from": "[a-aA-Z0-9-]", 487 | "required": true 488 | } 489 | ], 490 | "objects": [ 491 | { 492 | "kind": "DeploymentConfig", 493 | "apiVersion": "v1", 494 | "metadata": { 495 | "name": "ipcontroller-${CLUSTER_NAME}", 496 | "labels": { 497 | "appid": "jupyter-cluster-${CLUSTER_NAME}" 498 | } 499 | }, 500 | "spec": { 501 | "triggers": [ 502 | { 503 | "type": "ConfigChange" 504 | }, 505 | { 506 | "type": "ImageChange", 507 | "imageChangeParams": { 508 | "automatic": true, 509 | "containerNames": [ 510 | "ipcontroller-${CLUSTER_NAME}" 511 | ], 512 | "from": { 513 | "kind": "ImageStreamTag", 514 | "name": "${NOTEBOOK_IMAGE}" 515 | } 516 | } 517 | } 518 | ], 519 | "replicas": 1, 520 | "selector": { 521 | "deploymentconfig": "ipcontroller-${CLUSTER_NAME}" 522 | }, 523 | "template": { 524 | "metadata": { 525 | "labels": { 526 | "appid": "jupyter-cluster-${CLUSTER_NAME}", 527 | "deploymentconfig": "ipcontroller-${CLUSTER_NAME}" 528 | } 529 | }, 530 | "spec": { 531 | "containers": [ 532 | { 533 | "name": "ipcontroller-${CLUSTER_NAME}", 534 | "image": "${NOTEBOOK_IMAGE}", 535 | "ports": [ 536 | { 537 | "containerPort": 10000, 538 | "protocol": "TCP" 539 | }, 540 | { 541 | "containerPort": 10001, 542 | "protocol": "TCP" 543 | }, 544 | { 545 | "containerPort": 10002, 546 | "protocol": "TCP" 547 | }, 548 | { 549 | "containerPort": 10003, 550 | "protocol": "TCP" 551 | }, 552 | { 553 | "containerPort": 10004, 554 | "protocol": "TCP" 555 | }, 556 | { 557 | "containerPort": 10005, 558 | "protocol": "TCP" 559 | }, 560 | { 561 | "containerPort": 10006, 562 | "protocol": "TCP" 563 | }, 564 | { 565 | "containerPort": 10007, 566 | "protocol": "TCP" 567 | }, 568 | { 569 | "containerPort": 10008, 570 | "protocol": "TCP" 571 | }, 572 | { 573 | "containerPort": 10009, 574 | "protocol": "TCP" 575 | }, 576 | { 577 | "containerPort": 10010, 578 | "protocol": "TCP" 579 | }, 580 | { 581 | "containerPort": 10011, 582 | "protocol": "TCP" 583 | } 584 | ], 585 | "env": [ 586 | { 587 | "name": "IPYPARALLEL_SERVICE_TYPE", 588 | "value": "controller" 589 | }, 590 | { 591 | "name": "IPYPARALLEL_CONTROLLER_NAME", 592 | "value": "ipcontroller-${CLUSTER_NAME}" 593 | } 594 | ] 595 | } 596 | ] 597 | } 598 | } 599 | } 600 | }, 601 | { 602 | "kind": "DeploymentConfig", 603 | "apiVersion": "v1", 604 | "metadata": { 605 | "name": "ipengine-${CLUSTER_NAME}", 606 | "labels": { 607 | "appid": "jupyter-cluster-${CLUSTER_NAME}" 608 | } 609 | }, 610 | "spec": { 611 | "triggers": [ 612 | { 613 | "type": "ConfigChange" 614 | }, 615 | { 616 | "type": "ImageChange", 617 | "imageChangeParams": { 618 | "automatic": true, 619 | "containerNames": [ 620 | "ipengine-${CLUSTER_NAME}" 621 | ], 622 | "from": { 623 | "kind": "ImageStreamTag", 624 | "name": "${NOTEBOOK_IMAGE}" 625 | } 626 | } 627 | } 628 | ], 629 | "replicas": 1, 630 | "selector": { 631 | "deploymentconfig": "ipengine-${CLUSTER_NAME}" 632 | }, 633 | "template": { 634 | "metadata": { 635 | "labels": { 636 | "appid": "jupyter-cluster-${CLUSTER_NAME}", 637 | "deploymentconfig": "ipengine-${CLUSTER_NAME}" 638 | } 639 | }, 640 | "spec": { 641 | "containers": [ 642 | { 643 | "name": "ipengine-${CLUSTER_NAME}", 644 | "image": "${NOTEBOOK_IMAGE}", 645 | "ports": [ 646 | { 647 | "containerPort": 10000, 648 | "protocol": "TCP" 649 | }, 650 | { 651 | "containerPort": 10001, 652 | "protocol": "TCP" 653 | }, 654 | { 655 | "containerPort": 10002, 656 | "protocol": "TCP" 657 | }, 658 | { 659 | "containerPort": 10003, 660 | "protocol": "TCP" 661 | }, 662 | { 663 | "containerPort": 10004, 664 | "protocol": "TCP" 665 | }, 666 | { 667 | "containerPort": 10005, 668 | "protocol": "TCP" 669 | }, 670 | { 671 | "containerPort": 10006, 672 | "protocol": "TCP" 673 | }, 674 | { 675 | "containerPort": 10007, 676 | "protocol": "TCP" 677 | }, 678 | { 679 | "containerPort": 10008, 680 | "protocol": "TCP" 681 | }, 682 | { 683 | "containerPort": 10009, 684 | "protocol": "TCP" 685 | }, 686 | { 687 | "containerPort": 10010, 688 | "protocol": "TCP" 689 | }, 690 | { 691 | "containerPort": 10011, 692 | "protocol": "TCP" 693 | } 694 | ], 695 | "env": [ 696 | { 697 | "name": "IPYPARALLEL_SERVICE_TYPE", 698 | "value": "engine" 699 | }, 700 | { 701 | "name": "IPYPARALLEL_CONTROLLER_NAME", 702 | "value": "ipcontroller-${CLUSTER_NAME}" 703 | } 704 | ] 705 | } 706 | ] 707 | } 708 | } 709 | } 710 | }, 711 | { 712 | "kind": "Service", 713 | "apiVersion": "v1", 714 | "metadata": { 715 | "name": "ipcontroller-${CLUSTER_NAME}", 716 | "labels": { 717 | "appid": "jupyter-cluster-${CLUSTER_NAME}" 718 | } 719 | }, 720 | "spec": { 721 | "ports": [ 722 | { 723 | "name": "10000-tcp", 724 | "protocol": "TCP", 725 | "port": 10000, 726 | "targetPort": 10000 727 | }, 728 | { 729 | "name": "10001-tcp", 730 | "protocol": "TCP", 731 | "port": 10001, 732 | "targetPort": 10001 733 | }, 734 | { 735 | "name": "10002-tcp", 736 | "protocol": "TCP", 737 | "port": 10002, 738 | "targetPort": 10002 739 | }, 740 | { 741 | "name": "10003-tcp", 742 | "protocol": "TCP", 743 | "port": 10003, 744 | "targetPort": 10003 745 | }, 746 | { 747 | "name": "10004-tcp", 748 | "protocol": "TCP", 749 | "port": 10004, 750 | "targetPort": 10004 751 | }, 752 | { 753 | "name": "10005-tcp", 754 | "protocol": "TCP", 755 | "port": 10005, 756 | "targetPort": 10005 757 | }, 758 | { 759 | "name": "10006-tcp", 760 | "protocol": "TCP", 761 | "port": 10006, 762 | "targetPort": 10006 763 | }, 764 | { 765 | "name": "10007-tcp", 766 | "protocol": "TCP", 767 | "port": 10007, 768 | "targetPort": 10007 769 | }, 770 | { 771 | "name": "10008-tcp", 772 | "protocol": "TCP", 773 | "port": 10008, 774 | "targetPort": 10008 775 | }, 776 | { 777 | "name": "10009-tcp", 778 | "protocol": "TCP", 779 | "port": 10009, 780 | "targetPort": 10009 781 | }, 782 | { 783 | "name": "10010-tcp", 784 | "protocol": "TCP", 785 | "port": 10010, 786 | "targetPort": 10010 787 | }, 788 | { 789 | "name": "10011-tcp", 790 | "protocol": "TCP", 791 | "port": 10011, 792 | "targetPort": 10011 793 | } 794 | ], 795 | "selector": { 796 | "deploymentconfig": "ipcontroller-${CLUSTER_NAME}" 797 | } 798 | } 799 | }, 800 | { 801 | "kind": "Service", 802 | "apiVersion": "v1", 803 | "metadata": { 804 | "name": "ipengine-${CLUSTER_NAME}", 805 | "labels": { 806 | "appid": "jupyter-cluster-${CLUSTER_NAME}" 807 | } 808 | }, 809 | "spec": { 810 | "ports": [ 811 | { 812 | "name": "10000-tcp", 813 | "protocol": "TCP", 814 | "port": 10000, 815 | "targetPort": 10000 816 | }, 817 | { 818 | "name": "10001-tcp", 819 | "protocol": "TCP", 820 | "port": 10001, 821 | "targetPort": 10001 822 | }, 823 | { 824 | "name": "10002-tcp", 825 | "protocol": "TCP", 826 | "port": 10002, 827 | "targetPort": 10002 828 | }, 829 | { 830 | "name": "10003-tcp", 831 | "protocol": "TCP", 832 | "port": 10003, 833 | "targetPort": 10003 834 | }, 835 | { 836 | "name": "10004-tcp", 837 | "protocol": "TCP", 838 | "port": 10004, 839 | "targetPort": 10004 840 | }, 841 | { 842 | "name": "10005-tcp", 843 | "protocol": "TCP", 844 | "port": 10005, 845 | "targetPort": 10005 846 | }, 847 | { 848 | "name": "10006-tcp", 849 | "protocol": "TCP", 850 | "port": 10006, 851 | "targetPort": 10006 852 | }, 853 | { 854 | "name": "10007-tcp", 855 | "protocol": "TCP", 856 | "port": 10007, 857 | "targetPort": 10007 858 | }, 859 | { 860 | "name": "10008-tcp", 861 | "protocol": "TCP", 862 | "port": 10008, 863 | "targetPort": 10008 864 | }, 865 | { 866 | "name": "10009-tcp", 867 | "protocol": "TCP", 868 | "port": 10009, 869 | "targetPort": 10009 870 | }, 871 | { 872 | "name": "10010-tcp", 873 | "protocol": "TCP", 874 | "port": 10010, 875 | "targetPort": 10010 876 | }, 877 | { 878 | "name": "10011-tcp", 879 | "protocol": "TCP", 880 | "port": 10011, 881 | "targetPort": 10011 882 | } 883 | ], 884 | "selector": { 885 | "deploymentconfig": "ipengine-${CLUSTER_NAME}" 886 | } 887 | } 888 | } 889 | ] 890 | } 891 | ] 892 | } 893 | -------------------------------------------------------------------------------- /scripts/generate-images.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Generates the image stream resource definitions. 3 | 4 | ''' 5 | 6 | import powershift.resources as resources 7 | 8 | image_stream = resources.v1_ImageStream( 9 | metadata = resources.v1_ObjectMeta( 10 | name = 'jupyter-notebook', 11 | annotations = { 12 | 'openshift.io/display-name': 'Jupyter Notebook' 13 | } 14 | ), 15 | spec = resources.v1_ImageStreamSpec() 16 | ) 17 | 18 | image_stream.spec.tags.append( 19 | resources.v1_TagReference( 20 | name = '2.7', 21 | annotations = { 22 | 'openshift.io/display-name': 'Jupyter Notebook (Python 2.7)', 23 | 'description': 'Build and deploy custom Jupyter Notebook images for Python 2.7.', 24 | 'iconClass': 'icon-python', 25 | 'tags': 'builder,python,jupyter', 26 | 'supports':'python', 27 | 'version': '2.7', 28 | 'sampleRepo': 'https://github.com/ricardoduarte/python-for-developers.git' 29 | }, 30 | from_ = resources.v1_ObjectReference( 31 | kind = 'DockerImage', 32 | name = 'getwarped/s2i-notebook-python27:latest' 33 | ) 34 | ) 35 | ) 36 | 37 | image_stream.spec.tags.append( 38 | resources.v1_TagReference( 39 | name = '3.5', 40 | annotations = { 41 | 'openshift.io/display-name': 'Jupyter Notebook (Python 3.5)', 42 | 'description': 'Build and deploy custom Jupyter Notebook images for Python 3.5.', 43 | 'iconClass': 'icon-python', 44 | 'tags': 'builder,python,jupyter', 45 | 'supports':'python', 46 | 'version': '3.5', 47 | 'sampleRepo': 'https://github.com/ricardoduarte/python-for-developers.git' 48 | }, 49 | from_ = resources.v1_ObjectReference( 50 | kind = 'DockerImage', 51 | name = 'getwarped/s2i-notebook-python35:latest' 52 | ) 53 | ) 54 | ) 55 | 56 | image_stream.spec.tags.append( 57 | resources.v1_TagReference( 58 | name = 'latest', 59 | annotations = { 60 | 'openshift.io/display-name': 'Jupyter Notebook (Python 3.X)', 61 | 'description': 'Build and deploy custom Jupyter Notebook images for Python 3.X.', 62 | 'iconClass': 'icon-python', 63 | 'tags': 'builder,python,jupyter', 64 | 'supports':'python', 65 | 'sampleRepo': 'https://github.com/ricardoduarte/python-for-developers.git' 66 | }, 67 | from_ = resources.v1_ObjectReference( 68 | kind = 'ImageStreamTag', 69 | name = '3.5' 70 | ) 71 | ) 72 | ) 73 | 74 | resources.dump(image_stream, indent=4, sort_keys=True) 75 | --------------------------------------------------------------------------------