From ff834ceab792f05b558da919954eca8033d75449 Mon Sep 17 00:00:00 2001 From: fishyds Date: Thu, 20 Dec 2018 16:54:07 +0800 Subject: [PATCH] [V0.4.1 Release] Merge v0.4.1 branch back to Master (#509) * Update nnictl.py Fix the issue that nnictl --version via pip installation doesn't work * Update kubeflow training service document (#494) * Remove kubectl related document, add messages for kubeconfig * Add design section for kubeflow training service * Move the image files for PAI training service doc into img folder. * Update KubeflowMode.md (#498) Update KubeflowMode.md, small terms change * [V0.4.1 bug fix] Cannot run kubeflow training service due to trial_keeper change (#503) * Update kubeflow training service document * fix bug a that kubeflow trial job cannot run * upgrade version number (#499) * [V0.4.1 bug fix] Support read K8S config from KUBECONFIG environment variable (#507) * Add KUBCONFIG env variable support * In main.ts, throw cached error to make sure nnictl can show the error in stderr --- README.md | 4 ++-- docs/GetStarted.md | 2 +- docs/Installation.md | 2 +- docs/KubeflowMode.md | 13 ++++++++----- docs/PAIMode.md | 6 +++--- docs/img/kubeflow_training_design.png | Bin 0 -> 44723 bytes docs/{ => img}/nni_pai_joblist.jpg | Bin docs/{ => img}/nni_trial_hdfs_output.jpg | Bin docs/{ => img}/nni_webui_joblist.jpg | Bin src/nni_manager/main.ts | 1 + .../kubeflow/kubernetesApiClient.ts | 4 ++-- tools/nni_cmd/nnictl.py | 2 +- tools/nni_trial_tool/trial_keeper.py | 16 ++++++++-------- 13 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 docs/img/kubeflow_training_design.png rename docs/{ => img}/nni_pai_joblist.jpg (100%) rename docs/{ => img}/nni_trial_hdfs_output.jpg (100%) rename docs/{ => img}/nni_webui_joblist.jpg (100%) diff --git a/README.md b/README.md index 7b53514622..0f15d67a20 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The tool dispatches and runs trial jobs generated by tuning algorithms to search * We support Linux (Ubuntu 16.04 or higher), MacOS (10.14.1) in our current stage. * Run the following commands in an environment that has `python >= 3.5`, `git` and `wget`. ```bash - git clone -b v0.4 https://github.com/Microsoft/nni.git + git clone -b v0.4.1 https://github.com/Microsoft/nni.git cd nni source install.sh ``` @@ -51,7 +51,7 @@ For the system requirements of NNI, please refer to [Install NNI](docs/Installat The following example is an experiment built on TensorFlow. Make sure you have **TensorFlow installed** before running it. * Download the examples via clone the source code. ```bash - git clone -b v0.4 https://github.com/Microsoft/nni.git + git clone -b v0.4.1 https://github.com/Microsoft/nni.git ``` * Run the mnist example. ```bash diff --git a/docs/GetStarted.md b/docs/GetStarted.md index 548be1846e..b665d09830 100644 --- a/docs/GetStarted.md +++ b/docs/GetStarted.md @@ -18,7 +18,7 @@ * __Install NNI through source code__ - git clone -b v0.4 https://github.com/Microsoft/nni.git + git clone -b v0.4.1 https://github.com/Microsoft/nni.git cd nni source install.sh diff --git a/docs/Installation.md b/docs/Installation.md index 93f7911847..ef7400df4b 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -18,7 +18,7 @@ Currently we only support installation on Linux & Mac. * __Install NNI through source code__ - git clone -b v0.4 https://github.com/Microsoft/nni.git + git clone -b v0.4.1 https://github.com/Microsoft/nni.git cd nni source install.sh diff --git a/docs/KubeflowMode.md b/docs/KubeflowMode.md index 3b2e18d4d1..2c4721b971 100644 --- a/docs/KubeflowMode.md +++ b/docs/KubeflowMode.md @@ -1,13 +1,13 @@ **Run an Experiment on Kubeflow** === -Now NNI supports running experiment on [Kubeflow](https://github.com/kubeflow/kubeflow), called kubeflow mode. Before starting to use NNI kubeflow mode, you should have a kubernetes cluster, either on-prem or [Azure Kubernetes Service(AKS)](https://azure.microsoft.com/en-us/services/kubernetes-service/), a Ubuntu machine on which [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) is installed and configured to connect to your kubernetes cluster. If you are not familiar with kubernetes, [here](https://kubernetes.io/docs/tutorials/kubernetes-basics/) is a goot start. In kubeflow mode, your trial program will run as kubeflow job in kubernetes cluster. +Now NNI supports running experiment on [Kubeflow](https://github.com/kubeflow/kubeflow), called kubeflow mode. Before starting to use NNI kubeflow mode, you should have a kubernetes cluster, either on-prem or [Azure Kubernetes Service(AKS)](https://azure.microsoft.com/en-us/services/kubernetes-service/), a Ubuntu machine on which [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) is setup to connect to your kubernetes cluster. If you are not familiar with kubernetes, [here](https://kubernetes.io/docs/tutorials/kubernetes-basics/) is a goot start. In kubeflow mode, your trial program will run as kubeflow job in kubernetes cluster. ## Prerequisite for on-premises Kubernetes Service 1. A **Kubernetes** cluster using Kubernetes 1.8 or later. Follow this [guideline](https://kubernetes.io/docs/setup/) to set up Kubernetes 2. Download, set up, and deploy **Kubelow** to your Kubernetes cluster. Follow this [guideline](https://www.kubeflow.org/docs/started/getting-started/) to set up Kubeflow -3. Install **kubectl**, and configure to connect to your Kubernetes API server. Follow this [guideline](https://kubernetes.io/docs/tasks/tools/install-kubectl/) to install kubectl on Ubuntu +3. Prepare a **kubeconfig** file, which will be used by NNI to interact with your kubernetes API server. By default, NNI manager will use $(HOME)/.kube/config as kubeconfig file's path. You can also specify other kubeconfig files by setting the **KUBECONFIG** environment variable. Refer this [guideline]( https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig) to learn more about kubeconfig. 4. If your NNI trial job needs GPU resource, you should follow this [guideline](https://github.com/NVIDIA/k8s-device-plugin) to configure **Nvidia device plugin for Kubernetes**. -5. Install **NFS server** and export a general purpose mount (we recommend to map your NFS server path in `root_squash option`, otherwise permission issue may raise when nni copy files to NFS. Refer this [page](https://linux.die.net/man/5/exports) to learn what root_squash option is), or **Azure File Storage**. +5. Prepare a **NFS server** and export a general purpose mount (we recommend to map your NFS server path in `root_squash option`, otherwise permission issue may raise when nni copy files to NFS. Refer this [page](https://linux.die.net/man/5/exports) to learn what root_squash option is), or **Azure File Storage**. 6. Install **NFS client** on the machine where you install NNI and run nnictl to create experiment. Run this command to install NFSv4 client: ``` apt-get install nfs-common @@ -17,13 +17,16 @@ Now NNI supports running experiment on [Kubeflow](https://github.com/kubeflow/ku ## Prerequisite for Azure Kubernetes Service 1. NNI support kubeflow based on Azure Kubernetes Service, follow the [guideline](https://azure.microsoft.com/en-us/services/kubernetes-service/) to set up Azure Kubernetes Service. -2. Install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) and __kubectl__. Use `az login` to set azure account, and connect kubectl client to AKS, [refer](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough#connect-to-the-cluster). +2. Install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) and __kubectl__. Use `az login` to set azure account, and connect kubectl client to AKS, refer this [guideline](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough#connect-to-the-cluster). 3. Deploy kubeflow on Azure Kubernetes Service, follow the [guideline](https://www.kubeflow.org/docs/started/getting-started/). 4. Follow the [guideline](https://docs.microsoft.com/en-us/azure/storage/common/storage-quickstart-create-account?tabs=portal) to create azure file storage account. If you use Azure Kubernetes Service, nni need Azure Storage Service to store code files and the output files. 5. To access Azure storage service, nni need the access key of the storage account, and nni use [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) Service to protect your private key. Set up Azure Key Vault Service, add a secret to Key Vault to store the access key of Azure storage account. Follow this [guideline](https://docs.microsoft.com/en-us/azure/key-vault/quick-create-cli) to store the access key. ## Design -TODO +![](./img/kubeflow_training_design.png) +Kubeflow training service instantiates a kubernetes rest client to interact with your K8s cluster's API server. + +For each trial, we will upload all the files in your local codeDir path (configured in nni_config.yaml) together with NNI generated files like parameter.cfg into a storage volumn. Right now we support two kinds of storage volumns: [nfs](https://en.wikipedia.org/wiki/Network_File_System) and [azure file storage](https://azure.microsoft.com/en-us/services/storage/files/), you should configure the storage volumn in nni config yaml file. After files are prepared, Kubeflow training service will call K8S rest API to create kubeflow jobs ([tf-operator](https://github.com/kubeflow/tf-operator) job or [pytorch-operator](https://github.com/kubeflow/pytorch-operator) job) in K8S, and mount your storage volumn into the job's pod. Output files of kubeflow job, like stdout, stderr, trial.log or model files, will also be copied back to the storage volumn. NNI will show the storage volumn's URL for each trial in WebUI, to allow user browse the log files and job's output files. ## Run an experiment Use `examples/trials/mnist` as an example. The nni config yaml file's content is like: diff --git a/docs/PAIMode.md b/docs/PAIMode.md index fbdb681ed8..367f613d85 100644 --- a/docs/PAIMode.md +++ b/docs/PAIMode.md @@ -60,17 +60,17 @@ nnictl create --config exp_pai.yaml ``` to start the experiment in pai mode. NNI will create OpanPAI job for each trial, and the job name format is something like `nni_exp_{experiment_id}_trial_{trial_id}`. You can see the pai jobs created by NNI in your OpenPAI cluster's web portal, like: -![](./nni_pai_joblist.jpg) +![](./img/nni_pai_joblist.jpg) Notice: In pai mode, NNIManager will start a rest server and listen on a port which is your NNI WebUI's port plus 1. For example, if your WebUI port is `8080`, the rest server will listen on `8081`, to receive metrics from trial job running in Kubernetes. So you should `enable 8081` TCP port in your firewall rule to allow incoming traffic. Once a trial job is completed, you can goto NNI WebUI's overview page (like http://localhost:8080/oview) to check trial's information. Expand a trial information in trial list view, click the logPath link like: -![](./nni_webui_joblist.jpg) +![](./img/nni_webui_joblist.jpg) And you will be redirected to HDFS web portal to browse the output files of that trial in HDFS: -![](./nni_trial_hdfs_output.jpg) +![](./img/nni_trial_hdfs_output.jpg) You can see there're three fils in output folder: stderr, stdout, and trial.log diff --git a/docs/img/kubeflow_training_design.png b/docs/img/kubeflow_training_design.png new file mode 100644 index 0000000000000000000000000000000000000000..b2acc7adc434339a84f6a2eb45d01b2970a191f0 GIT binary patch literal 44723 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 literal 0 HcmV?d00001 diff --git a/docs/nni_pai_joblist.jpg b/docs/img/nni_pai_joblist.jpg similarity index 100% rename from docs/nni_pai_joblist.jpg rename to docs/img/nni_pai_joblist.jpg diff --git a/docs/nni_trial_hdfs_output.jpg b/docs/img/nni_trial_hdfs_output.jpg similarity index 100% rename from docs/nni_trial_hdfs_output.jpg rename to docs/img/nni_trial_hdfs_output.jpg diff --git a/docs/nni_webui_joblist.jpg b/docs/img/nni_webui_joblist.jpg similarity index 100% rename from docs/nni_webui_joblist.jpg rename to docs/img/nni_webui_joblist.jpg diff --git a/src/nni_manager/main.ts b/src/nni_manager/main.ts index 1944cc9956..5ca7336d25 100644 --- a/src/nni_manager/main.ts +++ b/src/nni_manager/main.ts @@ -109,6 +109,7 @@ mkDirP(getLogDir()).then(async () => { log.info(`Rest server listening on: ${restServer.endPoint}`); } catch (err) { log.error(`${err.stack}`); + throw err; } }).catch((err: Error) => { console.error(`Failed to create log dir: ${err.stack}`); diff --git a/src/nni_manager/training_service/kubeflow/kubernetesApiClient.ts b/src/nni_manager/training_service/kubeflow/kubernetesApiClient.ts index 43b308e2df..f6e2c00d6f 100644 --- a/src/nni_manager/training_service/kubeflow/kubernetesApiClient.ts +++ b/src/nni_manager/training_service/kubeflow/kubernetesApiClient.ts @@ -36,7 +36,7 @@ class GeneralK8sClient { protected readonly log: Logger = getLogger(); constructor() { - this.client = new K8SClient({ config: K8SConfig.fromKubeconfig(path.join(os.homedir(), '.kube', 'config')), version: '1.9'}); + this.client = new K8SClient({ config: K8SConfig.fromKubeconfig(), version: '1.9'}); this.client.loadSpec(); } @@ -58,7 +58,7 @@ abstract class KubeflowOperatorClient { protected crdSchema: any; constructor() { - this.client = new K8SClient({ config: K8SConfig.fromKubeconfig(path.join(os.homedir(), '.kube', 'config'))}); + this.client = new K8SClient({ config: K8SConfig.fromKubeconfig() }); this.client.loadSpec(); } diff --git a/tools/nni_cmd/nnictl.py b/tools/nni_cmd/nnictl.py index eb67b01d9c..e76c86a5cc 100644 --- a/tools/nni_cmd/nnictl.py +++ b/tools/nni_cmd/nnictl.py @@ -30,7 +30,7 @@ def nni_info(*args): if args[0].version: - print(pkg_resources.get_distribution('nnictl').version) + print(pkg_resources.get_distribution('nni').version) else: print('please run "nnictl {positional argument} --help" to see nnictl guidance') diff --git a/tools/nni_trial_tool/trial_keeper.py b/tools/nni_trial_tool/trial_keeper.py index dc449fa303..c4adeac434 100644 --- a/tools/nni_trial_tool/trial_keeper.py +++ b/tools/nni_trial_tool/trial_keeper.py @@ -43,13 +43,13 @@ def main_loop(args): stdout_file = open(STDOUT_FULL_PATH, 'a+') stderr_file = open(STDERR_FULL_PATH, 'a+') - try: - hdfs_client = HdfsClient(hosts='{0}:{1}'.format(args.pai_hdfs_host, '50070'), user_name=args.pai_user_name, timeout=5) - except Exception as e: - nni_log(LogType.Error, 'Create HDFS client error: ' + str(e)) - raise e - - copyHdfsDirectoryToLocal(args.nni_hdfs_exp_dir, os.getcwd(), hdfs_client) + if args.pai_hdfs_host is not None and args.nni_hdfs_exp_dir is not None: + try: + hdfs_client = HdfsClient(hosts='{0}:{1}'.format(args.pai_hdfs_host, '50070'), user_name=args.pai_user_name, timeout=5) + except Exception as e: + nni_log(LogType.Error, 'Create HDFS client error: ' + str(e)) + raise e + copyHdfsDirectoryToLocal(args.nni_hdfs_exp_dir, os.getcwd(), hdfs_client) # Notice: We don't appoint env, which means subprocess wil inherit current environment and that is expected behavior process = Popen(args.trial_command, shell = True, stdout = stdout_file, stderr = stderr_file) @@ -62,7 +62,7 @@ def main_loop(args): if retCode is not None: nni_log(LogType.Info, 'subprocess terminated. Exit code is {}. Quit'.format(retCode)) - if NNI_PLATFORM == 'pai': + if args.pai_hdfs_output_dir is not None: # Copy local directory to hdfs for OpenPAI nni_local_output_dir = os.environ['NNI_OUTPUT_DIR'] try: