{ "cells": [ { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04be97e-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04beafa-495a-11e8-a8d6-0242ac130002", "previous": null } }, "source": [ "# About: Notebooks for Hadoop Clusters README\n", "\n", "Literate Computing for Reproducible Infrastructure: インフラ運用をJupyter + Ansibleでおこなう際のお手本Notebookです。(Hadoop版)\n", "\n", "このリポジトリでは、HDP(Hortonworks Data Platform, https://jp.hortonworks.com/products/data-center/hdp/ )を利用してHadoopクラスタを構築し、運用するためのNotebook例を紹介しています。\n", "\n", "**なお、これらのNotebookはNIIクラウドチーム内で行っている作業の考え方を示すためのもので、環境によってはそのままでは動作しないものもあります。**\n", "\n", "----\n", "\n", "[![Creative Commons License](https://i.creativecommons.org/l/by/4.0/88x31.png)](http://creativecommons.org/licenses/by/4.0/)\n", "\n", "Literate-computing-Hadoop (c) by National Institute of Informatics\n", "\n", "Literate-computing-Hadoop is licensed under a\n", "Creative Commons Attribution 4.0 International License.\n", "\n", "You should have received a copy of the license along with this\n", "work. If not, see ." ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04beafa-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bec4e-495a-11e8-a8d6-0242ac130002", "previous": "a04be97e-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## 関連資料\n", "\n", "\n", "- [Jupyter notebook を用いた文芸的インフラ運用のススメ - SlideShare](http://www.slideshare.net/nobu758/jupyter-notebook-63167604)\n", "- [Literate Automation(文芸的自動化)についての考察 - めもめも](http://enakai00.hatenablog.com/entry/2016/04/22/204125)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bec4e-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bed66-495a-11e8-a8d6-0242ac130002", "previous": "a04beafa-495a-11e8-a8d6-0242ac130002" } }, "source": [ "# お手本Notebook\n", "\n", "お手本NotebookはこのNotebookと同じディレクトリにあります。Notebookは目的に応じて以下のような命名規則に則って名前がつけられています。\n", "\n", "- D(NN)\\_(Notebook名) ... インストール関連Notebook\n", "- O(NN)\\_(Notebook名) ... 運用関連Notebook\n", "- T(NN)\\_(Notebook名) ... テスト関連Notebook\n", "\n", "特に、**[D00_Prerequisites for Literate Computing via Notebooks](D00_Prerequisites for Literate Computing via Notebooks.ipynb)は、お手本Notebookが適用可能なNotebook環境、Bind対象であるかどうかを確認するためのNotebook**です。はじめに実施して、これらのお手本Notebookが利用可能な状態かを確認してみてください。" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bed66-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bee88-495a-11e8-a8d6-0242ac130002", "previous": "a04bec4e-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## お手本Notebookの構成\n", "\n", "このNotebookは、大きく分けて以下のような構成になっています。\n", "\n", "1. 収容設計の準備Notebook\n", "2. Hadoopマシン準備Notebook\n", "3. HadoopインストールNotebook\n", "4. Hadoop運用Notebook\n", "5. Hadoop動作確認Notebook\n", "\n", "これらのNotebookと、構築・運用する対象の関係は、以下のようになります。\n", "\n", "![images/list-hadoop-notebooks.png](images/list-hadoop-notebooks.png)\n", "\n", "*収容設計の準備Notebook* は、構築・運用対象とするシステム全体の収容設計をおこないます。ここで、どのようなマシンがあって(あるいは用意する必要があって)、それぞれのマシンにどのような役割を割り当てるかを明確化します。このプロセスによりAnsibleのInventory, 変数群を生成します。**ここで、Ansibleなど自動化ツールに与えるパラメータと、収容設計の関連を明確化**します。\n", "\n", "*Hadoopマシン準備Notebook* は、Hadoopのインストール対象のマシンを準備し、**Hadoop(HDP)のインストールに必要な前提条件を満たすよう各マシンのOS設定を調整**します。それぞれの要件については各Notebookを参照してください。\n", "\n", "*HadoopインストールNotebook* は、Hadoopおよびその周辺ツールのインストール手順を定義します。マシンの準備Notebookにより**前提条件が満たされた状態のマシンに対して各種ツールをインストールする**ことができます。\n", "\n", "*Hadoop運用Notebook* は、構築した環境を保守する手順を定義します。実際にクラスタを構築すると、ハードウェア故障が生じたりさまざまなトラブルが発生するので、それらの状態変化に応じてクラスタの健康状態を維持する必要があります。**公開したこれらのNotebookは運用における手順や考え方を紹介する一例であり、障害の具体的な発生状況に応じて修正しながら実施する必要があります。**\n", "\n", "*Hadoop動作確認Notebook* は、構築した環境に実際にデータをアップロードしてみたり、ジョブを実行してみたりする例を提供します。**HadoopインストールNotebookを通してインストールされた環境の使い方**を示す役割もあります。" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bee88-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04befb4-495a-11e8-a8d6-0242ac130002", "previous": "a04bed66-495a-11e8-a8d6-0242ac130002" } }, "source": [ "### 収容設計の準備Notebook\n", "\n", "Hadoopのように複数ノードからなるクラスタを構築・運用する際は、それぞれのノードにどのようなロール(役割)を割り振って、リソースを割り当てていくか(収容設計)が重要なポイントになります。収容設計を明確にしながらシステムを構築・運用していくことで、的確なシステムのスケールや、スムーズなトラブル対応をすることが可能になります。\n", "\n", "そこで、インストールの前に以下のNotebookにより、収容設計の確認をしながら、AnsibleのInventoryやgroup_varsといった各種パラメータを生成しています。\n", "\n", "- [D10_Hadoop - Set! Inventory](D10_Hadoop - Set! Inventory.ipynb)\n", "\n", "\n", "ここで生成されるパラメータは、以降の手順のNotebookで設定ファイル生成の際に利用されます。\n", "\n", "\n", "[![Structure](images/structure.png)](http://interactive.blockdiag.com/?compression=deflate&encoding=base64&src=eJyFk0FPhDAQhe_7K-peuLCoN5MVExMvXtRkvenGFDpAQ2lJO0tEs__dFkxoEOp5Zt57-V4mEyqvGacl-d4QUimDJslNR95MRVtIpUI47u0kOgBekEfZgUSl-3f5ZCeZUnU0TO-l4ZkAUmp1aj86qk1kJZDmNbB4LvUArVB9Y5UmGW__79KLoP3iUsUKszMcIflsRJwkiedK5rbPLWiKXMmAq7ezZjoxut0FsLijXkDKFKJNI2gGItVQgP6HJ9ndrfAcNRrFeNEfV7G7VGvwvBRxoQRz5Xgpg_W4WGHdk1ko2FtzCkuNjdclSMcebDCJhn9Ben0VKs6prdQ1i7Pg6SAFjyML6XJEHU2Jbn6pDboD9OFtCBmPtq9Ul4Dk0BuEZrsfRgv2bnDenH8AoFQymA)\n", "\n", "ここで定義されるパラメータには、以下のようなものがあります。\n", "\n", "- 各サービスに割り当てるリソース量の情報\n", "- 各サービスのインストールに使用する外部リソースの情報\n", "\n", "Hadoop構築Notebookでは、各マシンにどのサービスを載せて、どの程度のメモリやディスクを割り当てていくかは、 [hosts.csv](hosts.csv) のような表形式で定義しています。この情報に基づいて、AnsibleのPlaybookに与えるパラメータを定義しています。\n", "\n", "また、Hadoopのインストール時にどのリポジトリからパッケージを取ってくるかという情報も、システムの素性を理解するうえで重要な情報です。このような情報も含めてNotebookの形で明確化、証跡を残すようにしています。" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04befb4-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf0e0-495a-11e8-a8d6-0242ac130002", "previous": "a04bee88-495a-11e8-a8d6-0242ac130002" } }, "source": [ "### Hadoopマシン準備Notebook\n", "\n", "Hadoopをインストールする対象となるマシンの準備に関するNotebookです。\n", "\n", "#### VMの作成手順\n", "\n", "NIIクラウドチームでは、プライベートなベアメタルクラウド(仮想マシンではなく物理マシンを貸すIaaS)を持っています。このクラウド上でベアメタル(物理マシン)を複数台用意し、そこにHadoopをインストールする形を採っています。\n", "ベアメタルの設定手順の事例紹介は難しいのですが、テスト用クラスタを構築する際に使っているKVM環境の準備と、VMの構築用Notebookをここでは紹介しています。\n", "\n", "この例では、以下のものを準備しています。\n", "\n", "- [D03_KVM - Ready! on CentOS](D03_KVM - Ready! on CentOS.ipynb)\n", "- [D03b_KVM - Set! CentOS6](D03b_KVM - Set! CentOS6.ipynb)\n", "- [D03c_KVM - Go! VM](D03c_KVM - Go! VM.ipynb)\n", "\n", "\n", "また、Google Compute Engineでのマシン確保を想定したNotebookも添付しています。\n", "\n", "- [D01_GCE - Set! Go! (Google Compute Engine)](D01_GCE - Set! Go! %28Google Compute Engine%29.ipynb)\n", "- [O03_GCE - Destroy VM (Google Compute Engine)](O03_GCE - Destroy VM %28Google Compute Engine%29.ipynb)\n", "\n", "#### VMの設定手順\n", "\n", "作成したVMは、MinimalなOSがインストールされた状態ですので、(当然のことながら)セキュリティ上必要な設定を施す必要があります。\n", "\n", "- [D90_Postscript - Operational Policy Settings; Security etc. (to be elaborated)](D90_Postscript - Operational Policy Settings; Security etc. %28to be elaborated%29.ipynb)\n", "\n", "また、HDPをインストールするために必要な推奨設定などもあります。これらの設定を施すNotebookも紹介しています。\n", "\n", "- [D11_Hadoop Prerequisites - Ready! on CentOS6](D11_Hadoop Prerequisites - Ready! on CentOS6.ipynb)\n", "\n", "これらの設定としてどのような項目が必要かは、VMの初期設定により異なってきます。" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf0e0-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf216-495a-11e8-a8d6-0242ac130002", "previous": "a04befb4-495a-11e8-a8d6-0242ac130002" } }, "source": [ "### HadoopインストールNotebook\n", "\n", "Hadoopのインストールは以下のような手順で実施しています。\n", "HDPで提供されていないものについては、HDPインストール用Notebookとは別にNotebookを用意しています。\n", "\n", "- [D12_Hadoop - Ready! on CentOS6 - ZK,HDFS,YARN,HBase,Hive,Spark](D12_Hadoop - Ready! on CentOS6 - ZK,HDFS,YARN,HBase,Hive,Spark.ipynb)\n", "- [D13a_Hadoop Swimlanes - Ready! on Tez](D13a_Hadoop Swimlanes - Ready! on Tez.ipynb)\n", "- [D13b_Hivemall - Ready! on Hive](D13b_Hivemall - Ready! on Hive.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf216-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf342-495a-11e8-a8d6-0242ac130002", "previous": "a04bf0e0-495a-11e8-a8d6-0242ac130002" } }, "source": [ "### Hadoop運用Notebook\n", "\n", "Hadoopの運用に関しては、以下のようなNotebookを用意しています。\n", "\n", "- [O12a_Hadoop - Start the services - ZK,HDFS,YARN,HBase,Spark](O12a_Hadoop - Start the services - ZK,HDFS,YARN,HBase,Spark.ipynb)\n", "- [O12b_Hadoop - Stop the services - Spark,HBase,YARN,HDFS,ZK](O12b_Hadoop - Stop the services - Spark,HBase,YARN,HDFS,ZK.ipynb)\n", "- [O12c_Hadoop - Decommission DataNode](O12c_Hadoop - Decommission DataNode.ipynb)\n", "- [O12d_Hadoop - Restore a Slave Node](O12d_Hadoop - Restore a Slave Node.ipynb)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf342-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf46e-495a-11e8-a8d6-0242ac130002", "previous": "a04bf216-495a-11e8-a8d6-0242ac130002" } }, "source": [ "### Hadoop動作確認Notebook\n", "\n", "動作確認のためのNotebookとして、以下のものを用意しています。\n", "\n", "- [T12a_Hadoop - Confirm the services are alive - ZK,HDFS,YARN,HBase,Spark](T12a_Hadoop - Confirm the services are alive - ZK,HDFS,YARN,HBase,Spark.ipynb)\n", "- [T12b_Hadoop - Simple YARN for Test job](T12b_Hadoop - Simple YARN job for Test.ipynb)\n", "- [T12c_Hadoop - Simple HBase query for Test.ipynb](T12c_Hadoop - Simple HBase query for Test.ipynb)\n", "- [T12d_Hadoop - Simple Spark script for Test](T12d_Hadoop - Simple Spark script for Test.ipynb)\n", "- [T13b_Hadoop - Simple Hivemall query for Test](T13b_Hadoop - Simple Hivemall query for Test.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf46e-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf590-495a-11e8-a8d6-0242ac130002", "previous": "a04bf342-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## お手本Notebookの一覧\n", "\n", "現在、このNotebook環境からアクセス可能なNotebookの一覧を参照するには、以下のセルを実行(`Run cell`)してください。Notebookファイルへのリンクが表示されます。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "lc_cell_meme": { "current": "a04bf590-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf6bc-495a-11e8-a8d6-0242ac130002", "previous": "a04bf46e-495a-11e8-a8d6-0242ac130002" }, "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
D00_Prerequisites for Literate Computing via Notebooks
D01_GCE - Set! Go! (Google Compute Engine)
D03_KVM - Ready! on CentOS
D03b_KVM - Set! CentOS6
D03c_KVM - Go! VM
D10_Hadoop - Set! Inventory
D11_Hadoop Prerequisites - Ready! on CentOS6
D12_Hadoop - Ready! on CentOS6 - ZK,HDFS,YARN,HBase,Hive,Spark
D13a_Hadoop Swimlanes - Ready! on Tez
D13b_Hivemall - Ready! on Hive
D90_Postscript - Operational Policy Settings; Security etc. (to be elaborated)
O03_GCE - Destroy VM (Google Compute Engine)
O03_KVM - Destroy VM on KVM
O12a_Hadoop - Start the services - ZK,HDFS,YARN,HBase,Spark
O12b_Hadoop - Stop the services - Spark,HBase,YARN,HDFS,ZK
O12c_Hadoop - Decommission DataNode
O12d_Hadoop - Restore a Slave Node
T03_KVM - Confirm KVM is healthy
T03_KVM - Status Report of running VMs
T12a_Hadoop - Confirm the services are alive - ZK,HDFS,YARN,HBase,Spark
T12b_Hadoop - Simple YARN job for Test
T12c_Hadoop - Simple HBase query for Test
T12d_Hadoop - Simple Spark script for Test
T13b_Hadoop - Simple Hivemall query for Test
" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import re\n", "import os\n", "from IPython.core.display import HTML\n", "\n", "ref_notebooks = filter(lambda m: m, map(lambda n: re.match(r'([A-Z][0-9][0-9a-z]+_.*)\\.ipynb', n), os.listdir('.')))\n", "ref_notebooks = sorted(ref_notebooks, key=lambda m: m.group(1))\n", "HTML(''.join(map(lambda m: '
{title}
'.format(name=m.group(0), title=m.group(1)),\n", " ref_notebooks)))" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf6bc-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf7fc-495a-11e8-a8d6-0242ac130002", "previous": "a04bf590-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## お手本Notebookと証跡Notebook\n", "\n", "お手本Notebookを使う場合は、お手本をコピーし、そのコピーを開きます。このように、**お手本と作業証跡は明確に分けながら作業をおこないます。**\n", "\n", "また、お手本をコピーする際は、 `YYYYMMDD_NN_` といった実施日を示すプレフィックスを付加することで、後で整理しやすくしています。" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bf7fc-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bf91e-495a-11e8-a8d6-0242ac130002", "previous": "a04bf6bc-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## 実際にお手本Notebookを使ってみる\n", "\n", "以下のJavaScriptを実行することで、簡単にお手本から作業用Notebookを作成することもできます。\n", "\n", "以下のセルを実行すると、Notebook名のドロップダウンリストと[作業開始]ボタンが現れます。\n", "[作業開始]ボタンを押すと、お手本Notebookのコピーを作成した後、自動的にブラウザでコピーが開きます。\n", "Notebookの説明を確認しながら実行、適宜修正しながら実行していってください。" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "lc_cell_meme": { "current": "a04bf91e-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bfa40-495a-11e8-a8d6-0242ac130002", "previous": "a04bf7fc-495a-11e8-a8d6-0242ac130002" } }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from datetime import datetime\n", "import shutil\n", "\n", "def copy_ref_notebook(src):\n", " prefix = datetime.now().strftime('%Y%m%d') + '_'\n", " index = len(filter(lambda name: name.startswith(prefix), os.listdir('.'))) + 1\n", " new_notebook = '{0}{1:0>2}_{2}'.format(prefix, index, src)\n", " shutil.copyfile(src, new_notebook)\n", " print(new_notebook)\n", "\n", "frags = map(lambda m: ''.format(name=m.group(0), title=m.group(1)),\n", " ref_notebooks)\n", "HTML('''\n", "\n", "')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "lc_cell_meme": { "current": "a04bfa40-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bfb6c-495a-11e8-a8d6-0242ac130002", "previous": "a04bf91e-495a-11e8-a8d6-0242ac130002" } }, "source": [ "## お手本のアーカイブ\n", "\n", "以下のセルで、お手本NotebookのZIPアーカイブを作成できます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "lc_cell_meme": { "current": "a04bfb6c-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bfc8e-495a-11e8-a8d6-0242ac130002", "previous": "a04bfa40-495a-11e8-a8d6-0242ac130002" } }, "outputs": [], "source": [ "ref_notebooks = filter(lambda m: m, map(lambda n: re.match(r'([A-Z][0-9][0-9a-z]+_.*)\\.ipynb', n), os.listdir('.')))\n", "ref_notebooks = sorted(ref_notebooks, key=lambda m: m.group(1))\n", "!zip ref_notebooks-{datetime.now().strftime('%Y%m%d')}.zip README.ipynb hosts.csv {' '.join(map(lambda n: '\"' + n.group(0) + '\"', ref_notebooks))} scripts/* images/* group_vars/.gitkeep" ] }, { "cell_type": "markdown", "metadata": { "lc_cell_meme": { "current": "a04bfc8e-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bfdba-495a-11e8-a8d6-0242ac130002", "previous": "a04bfb6c-495a-11e8-a8d6-0242ac130002" } }, "source": [ "こいつを・・・以下のURLからダウンロードできます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "lc_cell_meme": { "current": "a04bfdba-495a-11e8-a8d6-0242ac130002", "history": [], "next": "a04bfedc-495a-11e8-a8d6-0242ac130002", "previous": "a04bfc8e-495a-11e8-a8d6-0242ac130002" } }, "outputs": [], "source": [ "HTML('{filename}' \\\n", " .format(filename='ref_notebooks-' + datetime.now().strftime('%Y%m%d') + '.zip'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "lc_cell_meme": { "current": "a04bfedc-495a-11e8-a8d6-0242ac130002", "history": [], "next": null, "previous": "a04bfdba-495a-11e8-a8d6-0242ac130002" } }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.9" }, "lc_notebook_meme": { "current": "a04be6d6-495a-11e8-a8d6-0242ac130002", "history": [], "root_cells": [ "a04be97e-495a-11e8-a8d6-0242ac130002", "a04beafa-495a-11e8-a8d6-0242ac130002", "a04bec4e-495a-11e8-a8d6-0242ac130002", "a04bed66-495a-11e8-a8d6-0242ac130002", "a04bee88-495a-11e8-a8d6-0242ac130002", "a04befb4-495a-11e8-a8d6-0242ac130002", "a04bf0e0-495a-11e8-a8d6-0242ac130002", "a04bf216-495a-11e8-a8d6-0242ac130002", "a04bf342-495a-11e8-a8d6-0242ac130002", "a04bf46e-495a-11e8-a8d6-0242ac130002", "a04bf590-495a-11e8-a8d6-0242ac130002", "a04bf6bc-495a-11e8-a8d6-0242ac130002", "a04bf7fc-495a-11e8-a8d6-0242ac130002", "a04bf91e-495a-11e8-a8d6-0242ac130002", "a04bfa40-495a-11e8-a8d6-0242ac130002", "a04bfb6c-495a-11e8-a8d6-0242ac130002", "a04bfc8e-495a-11e8-a8d6-0242ac130002", "a04bfdba-495a-11e8-a8d6-0242ac130002", "a04bfedc-495a-11e8-a8d6-0242ac130002" ] }, "toc": { "base_numbering": 1, "nav_menu": { "height": "297px", "width": "252px" }, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": "block", "toc_window_display": true }, "toc_position": { "height": "313px", "left": "1291.57px", "right": "20px", "top": "146.997px", "width": "293px" } }, "nbformat": 4, "nbformat_minor": 1 }