From 067082a2ae766e66e43f2dc0c533c9152bbfb42a Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Fri, 28 Apr 2017 18:59:18 +0800 Subject: [PATCH 1/4] WIP --- .gitignore | 1 + README.md | 17 +- paddlecloud/Dockerfile | 8 + paddlecloud/k8s/cloud_deployment.yaml | 16 ++ paddlecloud/k8s/cloud_ingress.yaml | 13 ++ paddlecloud/k8s/cloud_service.yaml | 11 ++ paddlecloud/nginx.conf | 10 +- paddlecloud/notebook/frame_middleware.py | 10 + paddlecloud/notebook/templates/notebook.html | 15 +- paddlecloud/notebook/utils.py | 185 ++++++++++++++++++ paddlecloud/notebook/views.py | 143 ++------------ paddlecloud/paddlecloud/settings.py | 1 + .../paddlecloud/templates/homepage.html | 2 +- .../paddlecloud/templates/site_base.html | 22 ++- .../paddlecloud/templatetags/__init__.py | 0 .../templatetags/notebook_status_tags.py | 29 +++ paddlecloud/paddlecloud/urls.py | 3 +- 17 files changed, 348 insertions(+), 138 deletions(-) create mode 100644 .gitignore create mode 100644 paddlecloud/Dockerfile create mode 100644 paddlecloud/k8s/cloud_deployment.yaml create mode 100644 paddlecloud/k8s/cloud_ingress.yaml create mode 100644 paddlecloud/k8s/cloud_service.yaml create mode 100644 paddlecloud/notebook/frame_middleware.py create mode 100644 paddlecloud/notebook/utils.py create mode 100644 paddlecloud/paddlecloud/templatetags/__init__.py create mode 100644 paddlecloud/paddlecloud/templatetags/notebook_status_tags.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..352f8380 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.Python diff --git a/README.md b/README.md index 724b0c21..2f8fcf2b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,18 @@ -# {{ project_name }} +# PaddlePaddle Cloud ## Getting Started +Make sure you have `Python > 2.7.10` installed. + Make sure you are using a virtual environment of some sort (e.g. `virtualenv` or `pyenv`). +``` +virtualenv paddlecloudenv +# enable the virtualenv +source paddlecloudenv/bin/activate +``` +To run for the first time, you need to: ``` npm install pip install -r requirements.txt @@ -13,4 +21,9 @@ pip install -r requirements.txt npm run dev ``` -Browse to http://localhost:3000/ +Browse to http://localhost:8000/ + +If you are starting the server for the second time, just run: +``` +./manage.py runserver +``` diff --git a/paddlecloud/Dockerfile b/paddlecloud/Dockerfile new file mode 100644 index 00000000..2df2b881 --- /dev/null +++ b/paddlecloud/Dockerfile @@ -0,0 +1,8 @@ +FROM python:2.7.13-alpine +RUN apk add --update -y nodejs + +ADD ./ /pcloud/ +RUN cd /pcloud && npm install && pip install -r requirements.txt && npm build +WORKDIR /pcloud + +CMD ["sh", "-c", "./manage.py migrate; ./manage.py loaddata sites; ./manage.py runserver 0.0.0.0:8000"] diff --git a/paddlecloud/k8s/cloud_deployment.yaml b/paddlecloud/k8s/cloud_deployment.yaml new file mode 100644 index 00000000..59cc997c --- /dev/null +++ b/paddlecloud/k8s/cloud_deployment.yaml @@ -0,0 +1,16 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: paddle-cloud +spec: + replicas: 1 + template: + metadata: + labels: + app: paddle-cloud + spec: + containers: + - name: paddle-cloud + image: docker.paddlepaddlehub.com/pcloud:latest + ports: + - containerPort: 8000 diff --git a/paddlecloud/k8s/cloud_ingress.yaml b/paddlecloud/k8s/cloud_ingress.yaml new file mode 100644 index 00000000..bfe8ae7e --- /dev/null +++ b/paddlecloud/k8s/cloud_ingress.yaml @@ -0,0 +1,13 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: paddle-cloud-ingress +spec: + rules: + - host: cloud.paddlepaddle.org + http: + paths: + - path: / + backend: + serviceName: paddle-cloud-service + servicePort: 8000 diff --git a/paddlecloud/k8s/cloud_service.yaml b/paddlecloud/k8s/cloud_service.yaml new file mode 100644 index 00000000..a0192013 --- /dev/null +++ b/paddlecloud/k8s/cloud_service.yaml @@ -0,0 +1,11 @@ +kind: Service +apiVersion: v1 +metadata: + name: paddle-cloud-service +spec: + selector: + app: paddle-cloud + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/paddlecloud/nginx.conf b/paddlecloud/nginx.conf index 77adb977..a416b864 100644 --- a/paddlecloud/nginx.conf +++ b/paddlecloud/nginx.conf @@ -37,12 +37,20 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; + location ~ ^/notebook/([a-z0-9]+) { + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://192.168.64.2:80; + } + location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://172.20.166.33:8000/; + proxy_pass http://172.20.166.33:8000; } } diff --git a/paddlecloud/notebook/frame_middleware.py b/paddlecloud/notebook/frame_middleware.py new file mode 100644 index 00000000..362d6f29 --- /dev/null +++ b/paddlecloud/notebook/frame_middleware.py @@ -0,0 +1,10 @@ +class NotebookMiddleware: + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response["Content-Security-Policy"] = "frame-ancestors 'self' http://notebook.paddlepaddle.org" + response["X-Frame-Options"] = "ALLOW-FROM http://notebook.paddlepaddle.org" + return response diff --git a/paddlecloud/notebook/templates/notebook.html b/paddlecloud/notebook/templates/notebook.html index ec073128..5e2408df 100644 --- a/paddlecloud/notebook/templates/notebook.html +++ b/paddlecloud/notebook/templates/notebook.html @@ -7,9 +7,16 @@ {% block body_class %}home{% endblock %} {% block body_base %} -
- -
- {{ notebook_id }} + + +
+ +
{% endblock %} diff --git a/paddlecloud/notebook/utils.py b/paddlecloud/notebook/utils.py new file mode 100644 index 00000000..2d88794f --- /dev/null +++ b/paddlecloud/notebook/utils.py @@ -0,0 +1,185 @@ +from django.conf import settings +import os +import kubernetes +import hashlib +import copy + +def email_escape(email): + """ + escape email to a safe string of kubernetes namespace + """ + safe_email = email.replace("@", "-") + safe_email = safe_email.replace(".", "-") + safe_email = safe_email.replace("_", "-") + return safe_email + + +def update_user_k8s_config(username): + """ + update kubernetes client to use current logined user's crednetials + """ + conf_obj = kubernetes.client.Configuration() + conf_obj.host = settings.K8S_HOST + conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) + conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, username, ".pem") + conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, username, "-key.pem") + kubernetes.config.load_kube_config(client_configuration=conf_obj) + +# a class for creating jupyter notebook resources +class UserNotebook(): + dep_body = { + "apiVersion": "extensions/v1beta1", + "kind": "Deployment", + "metadata": { + "name": "paddle-book-deployment" + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "labels": { + "app": "paddle-book" + } + }, + "spec": { + "containers": [{ + "name": "paddle-book", + "image": settings.PADDLE_BOOK_IMAGE, + "command": ["sh", "-c", + "mkdir -p /root/.jupyter; echo \"c.NotebookApp.base_url = '/notebook/%s'\" > /root/.jupyter/jupyter_notebook_config.py; jupyter notebook --debug --ip=0.0.0.0 --no-browser --allow-root --NotebookApp.token='' --NotebookApp.disable_check_xsrf=True /book/"], + "ports": [{ + "containerPort": settings.PADDLE_BOOK_PORT + }] + }] + } + } + } + } + service_body = { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "paddle-book-service" + }, + "spec": { + "selector": { + "app": "paddle-book" + }, + "ports": [{ + "protocol": "TCP", + "port": 8888, + "targetPort": 8888 + }] + } + } + ing_body = { + "apiVersion": "extensions/v1beta1", + "kind": "Ingress", + "metadata": { + "name": "paddle-book-ingress" + }, + "spec": { + "rules": [{ + "host": "cloud.paddlepaddle.org", + "http": { + "paths": [{ + "path": "/", + "backend": { + "serviceName": "paddle-book-service", + "servicePort": 8888 + } + }, + ] + } + }] + } + } + def get_notebook_id(self, username): + # notebook id is md5(username) + m = hashlib.md5() + m.update(username) + + return m.hexdigest()[:8] + + def __wait_api_response(self, resp): + print resp.status + + def __find_item(self, resource_list, match_name): + item_found = False + for item in resource_list.items: + if item.metadata.name == match_name: + item_found = True + return item_found + + def __create_deployment(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + dep_list = v1beta1api.list_namespaced_deployment(namespace) + if not self.__find_item(dep_list, "paddle-book-deployment"): + self.dep_body["spec"]["template"]["spec"]["containers"][0]["command"][2] = \ + self.dep_body["spec"]["template"]["spec"]["containers"][0]["command"][2] % self.get_notebook_id(username) + resp = v1beta1api.create_namespaced_deployment(namespace, body=self.dep_body, pretty=True) + self.__wait_api_response(resp) + + def __create_service(self, namespace): + v1api = kubernetes.client.CoreV1Api() + service_list = v1api.list_namespaced_service(namespace) + if not self.__find_item(service_list, "paddle-book-service"): + resp = v1api.create_namespaced_service(namespace, body=self.service_body) + self.__wait_api_response(resp) + + # service for notebook websocket + service_ws = copy.deepcopy(self.service_body) + service_ws["metadata"]["name"] = "paddle-book-service-ws" + if not self.__find_item(service_list, "paddle-book-service-ws"): + resp = v1api.create_namespaced_service(namespace, body=service_ws) + self.__wait_api_response(resp) + + def __create_ingress(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + ing_list = v1beta1api.list_namespaced_ingress(namespace) + if not self.__find_item(ing_list, "paddle-book-ingress"): + # FIXME: must split this for different users + self.ing_body["spec"]["rules"][0]["http"]["paths"][0]["path"] = "/notebook/" + self.get_notebook_id(username) + #self.ing_body["spec"]["rules"][0]["http"]["paths"][1]["path"] = "/notebook/" + self.get_notebook_id(username) + "/api/kernels/*" + resp = v1beta1api.create_namespaced_ingress(namespace, body=self.ing_body) + self.__wait_api_response(resp) + + def start_all(self, username, namespace): + """ + start deployment, service, ingress to start a notebook service for current user + """ + self.__create_deployment(username, namespace) + self.__create_service(namespace) + self.__create_ingress(username, namespace) + + def stop_all(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + v1api = kubernetes.client.CoreV1Api() + v1beta1api.delete_namespaced_deployment("paddle-book-deployment", namespace) + v1beta1api.delete_namespaced_ingress("paddle-book-ingress", namespace) + v1api.delete_namespaced_service("paddle-book-service", namespace) + + def status(self, namespace): + """ + check notebook deployment status + @return: running starting stopped + """ + v1api = kubernetes.client.CoreV1Api() + v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + d, s, i = (True, True, True) + dep_list = v1beta1api.list_namespaced_deployment(namespace) + if not self.__find_item(dep_list, "paddle-book-deployment"): + d = False + service_list = v1api.list_namespaced_service(namespace) + if not self.__find_item(service_list, "paddle-book-service"): + s = False + ing_list = v1beta1api.list_namespaced_ingress(namespace) + if not self.__find_item(ing_list, "paddle-book-ingress"): + i = False + + if d and s and i: + return "running" + elif d or s or i: + return "starting" + else: + return "stopped" diff --git a/paddlecloud/notebook/views.py b/paddlecloud/notebook/views.py index 4dc4c21a..f19f64f1 100644 --- a/paddlecloud/notebook/views.py +++ b/paddlecloud/notebook/views.py @@ -19,6 +19,7 @@ import logging import hashlib import kubernetes +import utils @receiver(post_save, sender=User) def handle_user_save(sender, instance, created, **kwargs): @@ -57,125 +58,6 @@ def generate_username(self, form): username = form.cleaned_data["email"] return username -# a class for creating jupyter notebook resources -class UserNotebook(): - dep_body = { - "apiVersion": "extensions/v1beta1", - "kind": "Deployment", - "metadata": { - "name": "paddle-book-deployment" - }, - "spec": { - "replicas": 1, - "template": { - "metadata": { - "labels": { - "app": "paddle-book" - } - }, - "spec": { - "containers": [{ - "name": "paddle-book", - "image": settings.PADDLE_BOOK_IMAGE, - "ports": [{ - "containerPort": settings.PADDLE_BOOK_PORT - }] - }] - } - } - } - } - service_body = { - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": "paddle-book-service" - }, - "spec": { - "selector": { - "app": "paddle-book" - }, - "ports": [{ - "protocol": "TCP", - "port": 8888, - "targetPort": 8888 - }] - } - } - ing_body = { - "apiVersion": "extensions/v1beta1", - "kind": "Ingress", - "metadata": { - "name": "paddle-book-ingress" - }, - "spec": { - "rules": [{ - "host": "notebook.paddlepaddle.org", - "http": { - "paths": [{ - "path": "/", - "backend": { - "serviceName": "paddle-book-service", - "servicePort": 8888 - } - }] - } - }] - } - } - def get_notebook_id(self, username): - # notebook id is md5(username) - m = hashlib.md5() - m.update(username) - - return m.hexdigest()[:8] - - def __wait_api_response(self, resp): - print resp.status - - def __find_item(self, resource_list, match_name): - item_found = False - for item in resource_list.items: - if item.metadata.name == match_name: - item_found = True - return item_found - - def __create_deployment(self, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() - dep_list = v1beta1api.list_namespaced_deployment(namespace) - if not self.__find_item(dep_list, "paddle-book-deployment"): - resp = v1beta1api.create_namespaced_deployment(namespace, body=self.dep_body, pretty=True) - self.__wait_api_response(resp) - - def __create_service(self, namespace): - v1api = kubernetes.client.CoreV1Api() - service_list = v1api.list_namespaced_service(namespace) - if not self.__find_item(service_list, "paddle-book-service"): - resp = v1api.create_namespaced_service(namespace, body=self.service_body) - self.__wait_api_response(resp) - - def __create_ingress(self, username, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() - ing_list = v1beta1api.list_namespaced_ingress(namespace) - if not self.__find_item(ing_list, "paddle-book-ingress"): - # FIXME: must split this for different users - #self.ing_body["spec"]["rules"][0]["http"]["paths"][0]["path"] = "/" + self.get_notebook_id(username) - resp = v1beta1api.create_namespaced_ingress(namespace, body=self.ing_body) - self.__wait_api_response(resp) - - def start_all(self, username, namespace): - self.__create_deployment(namespace) - self.__create_service(namespace) - self.__create_ingress(username, namespace) - -def email_escape(email): - """ - escape email to a safe string of kubernetes namespace - """ - safe_email = email.replace("@", "-") - safe_email = safe_email.replace(".", "-") - safe_email = safe_email.replace("_", "-") - return safe_email @login_required def notebook_view(request): @@ -186,17 +68,13 @@ def notebook_view(request): # NOTICE: username is the same to user's email # NOTICE: escape the username to safe string to create namespaces username = request.user.username - conf_obj = kubernetes.client.Configuration() - conf_obj.host = settings.K8S_HOST - conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) - conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, username, ".pem") - conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, username, "-key.pem") - kubernetes.config.load_kube_config(client_configuration=conf_obj) + utils.update_user_k8s_config(username) + # FIXME: notebook must be started under username's namespace v1api = kubernetes.client.CoreV1Api() namespaces = v1api.list_namespace() user_namespace_found = False - user_namespace = email_escape(username) + user_namespace = utils.email_escape(username) for ns in namespaces.items: # must commit to user's namespace if ns.metadata.name == user_namespace: @@ -209,7 +87,16 @@ def notebook_view(request): "name": user_namespace }}) - ub = UserNotebook() + ub = utils.UserNotebook() ub.start_all(username, user_namespace) - return render(request, "notebook.html", context={"notebook_id": ub.get_notebook_id(username)}) + return render(request, "notebook.html", + context={"notebook_id": ub.get_notebook_id(username)}) + +@login_required +def stop_notebook_backend(request): + username = request.user.username + utils.update_user_k8s_config(username) + ub = utils.UserNotebook() + ub.start_all(username, user_namespace) + return HttpResponseRedirect("/") diff --git a/paddlecloud/paddlecloud/settings.py b/paddlecloud/paddlecloud/settings.py index 21e8a2f7..878f20d9 100644 --- a/paddlecloud/paddlecloud/settings.py +++ b/paddlecloud/paddlecloud/settings.py @@ -114,6 +114,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "account.middleware.ExpiredPasswordMiddleware", + "notebook.frame_middleware.NotebookMiddleware" ] ROOT_URLCONF = "paddlecloud.urls" diff --git a/paddlecloud/paddlecloud/templates/homepage.html b/paddlecloud/paddlecloud/templates/homepage.html index 0e53943b..f8d82270 100644 --- a/paddlecloud/paddlecloud/templates/homepage.html +++ b/paddlecloud/paddlecloud/templates/homepage.html @@ -46,7 +46,7 @@

{% blocktrans %}What is PaddlePaddle Cloud?{% endblocktr Starter projects provide project layout, scaffolding, already integrated components and ready-to-go code. - Launch Paddle notebook + Launch Paddle notebook {% endblocktrans %}
diff --git a/paddlecloud/paddlecloud/templates/site_base.html b/paddlecloud/paddlecloud/templates/site_base.html index 00cc5d32..4aed4f69 100644 --- a/paddlecloud/paddlecloud/templates/site_base.html +++ b/paddlecloud/paddlecloud/templates/site_base.html @@ -3,7 +3,27 @@ {% load staticfiles %} {% load pinax_webanalytics_tags %} {% load i18n %} - +{% load notebook_status_tags %} + +{% block nav %} + {% get_user_notebook_status request.user as status %} + +{% endblock %} {% block styles %} diff --git a/paddlecloud/paddlecloud/templatetags/__init__.py b/paddlecloud/paddlecloud/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py new file mode 100644 index 00000000..c1455e54 --- /dev/null +++ b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py @@ -0,0 +1,29 @@ +from __future__ import unicode_literals +from django import template +from django.contrib.messages.utils import get_level_tags +from django.utils.encoding import force_text +from notebook.utils import email_escape, update_user_k8s_config, UserNotebook +import kubernetes + +LEVEL_TAGS = get_level_tags() + +register = template.Library() + + +def _get_notebook_id(self, username): + # notebook id is md5(username) + m = hashlib.md5() + m.update(username) + + return m.hexdigest()[:8] + +@register.simple_tag() +def get_user_notebook_status(user): + if not user.is_authenticated: + return "" + namespace = email_escape(user.email) + update_user_k8s_config(user.username) + + ub = UserNotebook() + + return ub.status(namespace) diff --git a/paddlecloud/paddlecloud/urls.py b/paddlecloud/paddlecloud/urls.py index 7287a7ed..a2b3db4f 100644 --- a/paddlecloud/paddlecloud/urls.py +++ b/paddlecloud/paddlecloud/urls.py @@ -13,7 +13,8 @@ url(r"^account/signup/$", notebook.views.SignupView.as_view(), name="account_signup"), url(r"^account/login/$", notebook.views.LoginView.as_view(), name="account_login"), url(r"^account/", include("account.urls")), - url(r"^notebook/", notebook.views.notebook_view), + url(r"^notedash/", notebook.views.notebook_view), + url(r"^notestop/", notebook.views.stop_notebook_backend), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From cc5d6691ecfbd6ef31345d3d86d8c91dd0c201e3 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Sun, 30 Apr 2017 14:15:28 +0800 Subject: [PATCH 2/4] load k8s memory dict --- paddlecloud/notebook/utils.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/paddlecloud/notebook/utils.py b/paddlecloud/notebook/utils.py index 2d88794f..91342a77 100644 --- a/paddlecloud/notebook/utils.py +++ b/paddlecloud/notebook/utils.py @@ -14,16 +14,19 @@ def email_escape(email): return safe_email -def update_user_k8s_config(username): +def get_user_api_client(username): """ update kubernetes client to use current logined user's crednetials """ + conf_obj = kubernetes.client.Configuration() conf_obj.host = settings.K8S_HOST conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, username, ".pem") conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, username, "-key.pem") - kubernetes.config.load_kube_config(client_configuration=conf_obj) + #kubernetes.config.load_kube_config(client_configuration=conf_obj) + api_client = kubernetes.client.ApiClient(config=conf_obj) + return api_client # a class for creating jupyter notebook resources class UserNotebook(): @@ -112,7 +115,7 @@ def __find_item(self, resource_list, match_name): return item_found def __create_deployment(self, username, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) dep_list = v1beta1api.list_namespaced_deployment(namespace) if not self.__find_item(dep_list, "paddle-book-deployment"): self.dep_body["spec"]["template"]["spec"]["containers"][0]["command"][2] = \ @@ -120,8 +123,8 @@ def __create_deployment(self, username, namespace): resp = v1beta1api.create_namespaced_deployment(namespace, body=self.dep_body, pretty=True) self.__wait_api_response(resp) - def __create_service(self, namespace): - v1api = kubernetes.client.CoreV1Api() + def __create_service(self, username, namespace): + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) service_list = v1api.list_namespaced_service(namespace) if not self.__find_item(service_list, "paddle-book-service"): resp = v1api.create_namespaced_service(namespace, body=self.service_body) @@ -135,7 +138,7 @@ def __create_service(self, namespace): self.__wait_api_response(resp) def __create_ingress(self, username, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) ing_list = v1beta1api.list_namespaced_ingress(namespace) if not self.__find_item(ing_list, "paddle-book-ingress"): # FIXME: must split this for different users @@ -149,12 +152,12 @@ def start_all(self, username, namespace): start deployment, service, ingress to start a notebook service for current user """ self.__create_deployment(username, namespace) - self.__create_service(namespace) + self.__create_service(username, namespace) self.__create_ingress(username, namespace) def stop_all(self, username, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() - v1api = kubernetes.client.CoreV1Api() + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) v1beta1api.delete_namespaced_deployment("paddle-book-deployment", namespace) v1beta1api.delete_namespaced_ingress("paddle-book-ingress", namespace) v1api.delete_namespaced_service("paddle-book-service", namespace) @@ -164,8 +167,8 @@ def status(self, namespace): check notebook deployment status @return: running starting stopped """ - v1api = kubernetes.client.CoreV1Api() - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) d, s, i = (True, True, True) dep_list = v1beta1api.list_namespaced_deployment(namespace) if not self.__find_item(dep_list, "paddle-book-deployment"): From c6d433663423dfc131d1976da48816ed4373faae Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Mon, 1 May 2017 13:08:57 +0800 Subject: [PATCH 3/4] update --- .gitignore | 1 + paddlecloud/Dockerfile | 4 ++-- paddlecloud/ca.crt | 18 ------------------ paddlecloud/k8s/cloud_deployment.yaml | 7 +++++++ paddlecloud/notebook/tls.py | 7 ++++--- paddlecloud/notebook/utils.py | 6 +++--- paddlecloud/notebook/views.py | 13 ++++++------- paddlecloud/paddlecloud/settings.py | 9 ++++----- .../templatetags/notebook_status_tags.py | 7 +++---- 9 files changed, 30 insertions(+), 42 deletions(-) delete mode 100644 paddlecloud/ca.crt diff --git a/.gitignore b/.gitignore index 352f8380..4c0deeb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .Python +*.crt diff --git a/paddlecloud/Dockerfile b/paddlecloud/Dockerfile index 2df2b881..adba1175 100644 --- a/paddlecloud/Dockerfile +++ b/paddlecloud/Dockerfile @@ -1,8 +1,8 @@ FROM python:2.7.13-alpine RUN apk add --update -y nodejs -ADD ./ /pcloud/ +ADD requirements.txt package.json /pcloud/ RUN cd /pcloud && npm install && pip install -r requirements.txt && npm build WORKDIR /pcloud -CMD ["sh", "-c", "./manage.py migrate; ./manage.py loaddata sites; ./manage.py runserver 0.0.0.0:8000"] +CMD ["sh", "-c", "./manage.py migrate; ./manage.py loaddata sites; npm run dev"] diff --git a/paddlecloud/ca.crt b/paddlecloud/ca.crt deleted file mode 100644 index 428f5f43..00000000 --- a/paddlecloud/ca.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p -a3ViZUNBMB4XDTE3MDQxODA2MDMxNFoXDTI3MDQxNjA2MDMxNFowFTETMBEGA1UE -AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKyM -dvvKvO9q/qkiXhb0cBwyvfW3/wCVttmjjnysA0+eeza/7FVYoHXK8MZBP748Iden -T+DocKxxiyJ8wpa4DLJlbgMOWfWinZvZHeILmzblq/6rcLYe28TdkwUCgkJBhJaH -jcPYg6flYom2NbS2tVxajt3dZiCl6HUF0TTOOnULHK5+ATRu+DaQSKmBW9Ro0SM5 -UDr2Q7gBXcql5dsJDRiqWA2tzn3/GqSTcXT5IAA5GXs3cN1yvxSH/PbNFyx8QrK0 -YDzRzinvzQcrTJcLDV52Oxgo/bkWBqvtZYON4ATxB1gRvKvHMK7hcmQdfXv/nqh5 -IRQOdA35RmleofmfPyUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW -MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQCcfTBVHvVDkImrx5blMqrMH7OMYYQlzZiXaliTgwMBKgSs7zGV -dtYokFRVwHnniK677MU2BQ3DBrQq9SxwdVG4DvuMuOjmfuJWtSIgnmvBXQlKqh+Y -eyPgLAZTjsun+iFHye4KhcmE01A42Gp//zBMUhX9SC48QtsFLG94H/DNcy/zO7Qc -NAlNmazHv27cy4am9Cd/rWSTt3t3NkuDEYM/LW11mIdR8P1MbbsTCQyI2ayKNccU -0FplEqsjFQIGrW00vMPqKIvNK3Z2l1lR9O5s/U8p3k05aVocpMbRZb2A+TxnQcDp -s8fv8SswDNmK1fXxQ7o2DFWM5Ck1jW7ighph ------END CERTIFICATE----- diff --git a/paddlecloud/k8s/cloud_deployment.yaml b/paddlecloud/k8s/cloud_deployment.yaml index 59cc997c..9a442a15 100644 --- a/paddlecloud/k8s/cloud_deployment.yaml +++ b/paddlecloud/k8s/cloud_deployment.yaml @@ -9,8 +9,15 @@ spec: labels: app: paddle-cloud spec: + volumes: + - name: pcloud-volume + hostPath: + path: "/Users/wuyi/go/src/github.com/typhoonzero/PaddleCloud/paddlecloud" containers: - name: paddle-cloud + volumeMounts: + - mountPath: /pcloud + name: pcloud-volume image: docker.paddlepaddlehub.com/pcloud:latest ports: - containerPort: 8000 diff --git a/paddlecloud/notebook/tls.py b/paddlecloud/notebook/tls.py index ad738cdb..5322362f 100644 --- a/paddlecloud/notebook/tls.py +++ b/paddlecloud/notebook/tls.py @@ -1,5 +1,6 @@ import subprocess import os +from django.conf import settings def __check_cert_requirements__(program): def is_exe(fpath): @@ -29,13 +30,13 @@ def create_user_cert(ca_path, username): user_cert_cmds = [] user_cert_dir = os.path.join(settings.USER_CERTS_PATH, username) user_cert_cmds.append("mkdir -p %s" % user_cert_dir) - user_cert_cmd.append("openssl genrsa -out \ + user_cert_cmds.append("openssl genrsa -out \ %s/%s-key.pem 2048"%(user_cert_dir, username)) - user_cert_cmd.append("openssl req -new -key %s/%s-key.pem -out\ + user_cert_cmds.append("openssl req -new -key %s/%s-key.pem -out\ %s/%s.csr -subj \"/CN=%s\""%\ (user_cert_dir, username, user_cert_dir, username, username)) - user_cert_cmd.append("openssl x509 -req -in %s/%s.csr -CA %s -CAkey %s \ + user_cert_cmds.append("openssl x509 -req -in %s/%s.csr -CA %s -CAkey %s \ -CAcreateserial -out %s/%s.pem -days 365"% \ (user_cert_dir, username, settings.CA_PATH, settings.CA_KEY_PATH, diff --git a/paddlecloud/notebook/utils.py b/paddlecloud/notebook/utils.py index 91342a77..a34f74db 100644 --- a/paddlecloud/notebook/utils.py +++ b/paddlecloud/notebook/utils.py @@ -22,8 +22,8 @@ def get_user_api_client(username): conf_obj = kubernetes.client.Configuration() conf_obj.host = settings.K8S_HOST conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) - conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, username, ".pem") - conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, username, "-key.pem") + conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, "%s.pem"%username) + conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, "%s-key.pem"%username) #kubernetes.config.load_kube_config(client_configuration=conf_obj) api_client = kubernetes.client.ApiClient(config=conf_obj) return api_client @@ -162,7 +162,7 @@ def stop_all(self, username, namespace): v1beta1api.delete_namespaced_ingress("paddle-book-ingress", namespace) v1api.delete_namespaced_service("paddle-book-service", namespace) - def status(self, namespace): + def status(self, username, namespace): """ check notebook deployment status @return: running starting stopped diff --git a/paddlecloud/notebook/views.py b/paddlecloud/notebook/views.py index f19f64f1..b0145c69 100644 --- a/paddlecloud/notebook/views.py +++ b/paddlecloud/notebook/views.py @@ -36,11 +36,11 @@ class SignupView(account.views.SignupView): def after_signup(self, form): self.update_profile(form) - try: - logging.info("creating default user certs...") - tls.create_user_cert(settings.CA_PATH, form.get_user()) - except Exception, e: - logging.error("create user certs error: %s", str(e)) + #try: + logging.info("creating default user certs...") + tls.create_user_cert(settings.CA_PATH, form.cleaned_data["email"]) + #except Exception, e: + # logging.error("create user certs error: %s", str(e)) super(SignupView, self).after_signup(form) @@ -68,10 +68,9 @@ def notebook_view(request): # NOTICE: username is the same to user's email # NOTICE: escape the username to safe string to create namespaces username = request.user.username - utils.update_user_k8s_config(username) # FIXME: notebook must be started under username's namespace - v1api = kubernetes.client.CoreV1Api() + v1api = kubernetes.client.CoreV1Api(utils.get_user_api_client(username)) namespaces = v1api.list_namespace() user_namespace_found = False user_namespace = utils.email_escape(username) diff --git a/paddlecloud/paddlecloud/settings.py b/paddlecloud/paddlecloud/settings.py index 878f20d9..18c53baa 100644 --- a/paddlecloud/paddlecloud/settings.py +++ b/paddlecloud/paddlecloud/settings.py @@ -15,7 +15,7 @@ } ALLOWED_HOSTS = [ - "localhost", + "127.0.0.1", ] # Local time zone for this installation. Choices can be found here: @@ -198,12 +198,11 @@ "account.auth_backends.UsernameAuthenticationBackend", ] -ALLOWED_HOSTS=["cloud.paddlepaddle.org"] -CA_PATH = os.path.join(BASE_DIR, "ca.crt") -CA_KEY_PATH = os.path.join(BASE_DIR, "ca.key") +CA_PATH = os.path.join(BASE_DIR, "..", "ca.crt") +CA_KEY_PATH = os.path.join(BASE_DIR, "..", "ca.key") USER_CERTS_PATH="/tmp" -K8S_HOST = "https://192.168.64.2:8443" +K8S_HOST = "https://192.168.99.100:8443" PADDLE_BOOK_IMAGE="docker.paddlepaddle.org/book:0.10.0rc2" PADDLE_BOOK_PORT=8888 diff --git a/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py index c1455e54..8125be08 100644 --- a/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py +++ b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py @@ -2,7 +2,7 @@ from django import template from django.contrib.messages.utils import get_level_tags from django.utils.encoding import force_text -from notebook.utils import email_escape, update_user_k8s_config, UserNotebook +from notebook.utils import email_escape, UserNotebook, get_user_api_client import kubernetes LEVEL_TAGS = get_level_tags() @@ -21,9 +21,8 @@ def _get_notebook_id(self, username): def get_user_notebook_status(user): if not user.is_authenticated: return "" + username = user.username namespace = email_escape(user.email) - update_user_k8s_config(user.username) - ub = UserNotebook() - return ub.status(namespace) + return ub.status(username, namespace) From 6a8bd2e6f1fabe9e4293b491db8f11c843662d54 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Mon, 1 May 2017 18:56:17 +0800 Subject: [PATCH 4/4] Workable version --- README.md | 30 ++++++++++++++++++++++++++- paddlecloud/Dockerfile | 2 +- paddlecloud/k8s/cloud_deployment.yaml | 1 + paddlecloud/paddlecloud/settings.py | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f8fcf2b..95f1abef 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,35 @@ ## Getting Started -Make sure you have `Python > 2.7.10` installed. +### Pre-Requirements +- PaddlePaddle Cloud needs python to support `OPENSSL 1.2`. To check it out, simply run: + ```python + >>> import ssl + >>> ssl.OPENSSL_VERSION + 'OpenSSL 1.0.2k 26 Jan 2017' + ``` +- Make sure you have `Python > 2.7.10` installed. +### Run on kubernetes +```bash +# build docker image +git clone https://github.com/PaddlePaddle/cloud.git +cd cloud/paddlecloud +docker build -t [your_docker_registry]/pcloud . +docker push [your_docker_registry]/pcloud +# submit to kubernetes +kubectl create -f ./k8s +``` + +To test or visit the web site, find out the kubernetes [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) ip addresses, and bind it to your `/etc/hosts` file: +``` +# your ingress ip address +192.168.1.100 cloud.paddlepaddle.org +``` + +Then open your browser and visit http://cloud.paddlepaddle.org. + +### Run locally Make sure you are using a virtual environment of some sort (e.g. `virtualenv` or `pyenv`). ``` @@ -27,3 +54,4 @@ If you are starting the server for the second time, just run: ``` ./manage.py runserver ``` + diff --git a/paddlecloud/Dockerfile b/paddlecloud/Dockerfile index adba1175..eff2cb3c 100644 --- a/paddlecloud/Dockerfile +++ b/paddlecloud/Dockerfile @@ -1,5 +1,5 @@ FROM python:2.7.13-alpine -RUN apk add --update -y nodejs +RUN apk add --update nodejs openssl ADD requirements.txt package.json /pcloud/ RUN cd /pcloud && npm install && pip install -r requirements.txt && npm build diff --git a/paddlecloud/k8s/cloud_deployment.yaml b/paddlecloud/k8s/cloud_deployment.yaml index 9a442a15..77af650d 100644 --- a/paddlecloud/k8s/cloud_deployment.yaml +++ b/paddlecloud/k8s/cloud_deployment.yaml @@ -15,6 +15,7 @@ spec: path: "/Users/wuyi/go/src/github.com/typhoonzero/PaddleCloud/paddlecloud" containers: - name: paddle-cloud + imagePullPolicy: Always volumeMounts: - mountPath: /pcloud name: pcloud-volume diff --git a/paddlecloud/paddlecloud/settings.py b/paddlecloud/paddlecloud/settings.py index 18c53baa..1ecf97c1 100644 --- a/paddlecloud/paddlecloud/settings.py +++ b/paddlecloud/paddlecloud/settings.py @@ -16,6 +16,7 @@ ALLOWED_HOSTS = [ "127.0.0.1", + "cloud.paddlepaddle.org", ] # Local time zone for this installation. Choices can be found here: