From 836a244a136b29dbbcd63ab76c2ad1bd83a9bdcc Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 15 Aug 2023 17:31:42 +0100 Subject: [PATCH 01/64] Repo setup --- .github/workflows/automatic-doc-checks.yml | 65 +++++ .../workflows/automatic-doc-checks.yml | 65 +++++ docs/.gitignore | 8 + docs/.readthedocs.yaml | 27 ++ docs/.sphinx/_static/custom.css | 189 +++++++++++++ docs/.sphinx/_static/favicon.png | Bin 0 -> 57806 bytes docs/.sphinx/_static/github_issue_links.css | 24 ++ docs/.sphinx/_static/github_issue_links.js | 26 ++ docs/.sphinx/_static/header-nav.js | 10 + docs/.sphinx/_static/header.css | 167 ++++++++++++ docs/.sphinx/_static/tag.png | Bin 0 -> 6781 bytes docs/.sphinx/_templates/base.html | 7 + docs/.sphinx/_templates/footer.html | 90 ++++++ docs/.sphinx/_templates/header.html | 36 +++ docs/.sphinx/_templates/page.html | 49 ++++ docs/.sphinx/pinned-requirements.txt | 57 ++++ docs/.sphinx/requirements.txt | 13 + docs/.sphinx/spellingcheck.yaml | 27 ++ docs/.wokeignore | 4 + docs/.wordlist.txt | 17 ++ docs/Makefile | 65 +++++ docs/conf.py | 177 ++++++++++++ docs/custom_conf.py | 126 +++++++++ docs/doc-cheat-sheet-myst.md | 243 +++++++++++++++++ docs/doc-cheat-sheet.rst | 258 ++++++++++++++++++ docs/index.rst | 20 ++ docs/make.bat | 35 +++ docs/readme.rst | 236 ++++++++++++++++ docs/reuse/links.txt | 4 + 29 files changed, 2045 insertions(+) create mode 100644 .github/workflows/automatic-doc-checks.yml create mode 100644 docs/.github/workflows/automatic-doc-checks.yml create mode 100644 docs/.gitignore create mode 100644 docs/.readthedocs.yaml create mode 100644 docs/.sphinx/_static/custom.css create mode 100644 docs/.sphinx/_static/favicon.png create mode 100644 docs/.sphinx/_static/github_issue_links.css create mode 100644 docs/.sphinx/_static/github_issue_links.js create mode 100644 docs/.sphinx/_static/header-nav.js create mode 100644 docs/.sphinx/_static/header.css create mode 100644 docs/.sphinx/_static/tag.png create mode 100644 docs/.sphinx/_templates/base.html create mode 100644 docs/.sphinx/_templates/footer.html create mode 100644 docs/.sphinx/_templates/header.html create mode 100644 docs/.sphinx/_templates/page.html create mode 100644 docs/.sphinx/pinned-requirements.txt create mode 100644 docs/.sphinx/requirements.txt create mode 100644 docs/.sphinx/spellingcheck.yaml create mode 100644 docs/.wokeignore create mode 100644 docs/.wordlist.txt create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/custom_conf.py create mode 100644 docs/doc-cheat-sheet-myst.md create mode 100644 docs/doc-cheat-sheet.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/readme.rst create mode 100644 docs/reuse/links.txt diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml new file mode 100644 index 00000000..0c197638 --- /dev/null +++ b/.github/workflows/automatic-doc-checks.yml @@ -0,0 +1,65 @@ +name: Automatic documentation checks + +on: + - push + - pull_request + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + spellcheck: + name: Spelling check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Aspell + run: | + sudo apt-get install aspell aspell-en + + - name: Install the doc framework + working-directory: docs + run: | + make install + + - name: Build docs and run spelling checker + working-directory: docs + run: | + make spelling + + woke: + name: Inclusive language check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install the doc framework + working-directory: docs + run: | + make install + + - name: Run incluse-language checker + working-directory: docs + run: | + make woke + + linkcheck: + name: Link check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install the doc framework + working-directory: docs + run: | + make install + + - name: Run linkchecker + working-directory: docs + run: | + make linkcheck diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml new file mode 100644 index 00000000..5125782a --- /dev/null +++ b/docs/.github/workflows/automatic-doc-checks.yml @@ -0,0 +1,65 @@ +name: Automatic documentation checks + +on: + - push + - pull_request + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + spellcheck: + name: Spelling check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Aspell + run: | + sudo apt-get install aspell aspell-en + + - name: Install the doc framework + working-directory: . + run: | + make install + + - name: Build docs and run spelling checker + working-directory: . + run: | + make spelling + + woke: + name: Inclusive language check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install the doc framework + working-directory: . + run: | + make install + + - name: Run incluse-language checker + working-directory: . + run: | + make woke + + linkcheck: + name: Link check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install the doc framework + working-directory: . + run: | + make install + + - name: Run linkchecker + working-directory: . + run: | + make linkcheck diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..c8f8ee69 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,8 @@ +/*env*/ +.sphinx/venv +.sphinx/warnings.txt +.sphinx/.wordlist.dic +_build +.DS_Store +__pycache__ +.idea/ diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml new file mode 100644 index 00000000..45fe3a88 --- /dev/null +++ b/docs/.readthedocs.yaml @@ -0,0 +1,27 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: dirhtml + configuration: docs/conf.py + fail_on_warning: true + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/.sphinx/requirements.txt diff --git a/docs/.sphinx/_static/custom.css b/docs/.sphinx/_static/custom.css new file mode 100644 index 00000000..cad94b74 --- /dev/null +++ b/docs/.sphinx/_static/custom.css @@ -0,0 +1,189 @@ +/** Fix the font weight (300 for normal, 400 for slightly bold) **/ + +div.page, h1, h2, h3, h4, h5, h6, .sidebar-tree .current-page>.reference, button, input, optgroup, select, textarea, th.head { + font-weight: 300 +} + +.toc-tree li.scroll-current>.reference, dl.glossary dt, dl.simple dt, dl:not([class]) dt { + font-weight: 400; +} + +/** Table styling **/ + +th.head { + text-transform: uppercase; + font-size: var(--font-size--small); +} + +table.docutils { + border: 0; + box-shadow: none; + width:100%; +} + +table.docutils td, table.docutils th, table.docutils td:last-child, table.docutils th:last-child, table.docutils td:first-child, table.docutils th:first-child { + border-right: none; + border-left: none; +} + +/* Allow to centre text horizontally in table data cells */ +table.align-center { + text-align: center !important; +} + +/** No rounded corners **/ + +.admonition, code.literal, .sphinx-tabs-tab, .sphinx-tabs-panel, .highlight { + border-radius: 0; +} + +/** Admonition styling **/ + +.admonition { + border-top: 1px solid #d9d9d9; + border-right: 1px solid #d9d9d9; + border-bottom: 1px solid #d9d9d9; +} + +/** Color for the "copy link" symbol next to headings **/ + +a.headerlink { + color: var(--color-brand-primary); +} + +/** Line to the left of the current navigation entry **/ + +.sidebar-tree li.current-page { + border-left: 2px solid var(--color-brand-primary); +} + +/** Some tweaks for issue #16 **/ + +[role="tablist"] { + border-bottom: 1px solid var(--color-sidebar-item-background--hover); +} + +.sphinx-tabs-tab[aria-selected="true"] { + border: 0; + border-bottom: 2px solid var(--color-brand-primary); + background-color: var(--color-sidebar-item-background--current); + font-weight:300; +} + +.sphinx-tabs-tab{ + color: var(--color-brand-primary); + font-weight:300; +} + +.sphinx-tabs-panel { + border: 0; + border-bottom: 1px solid var(--color-sidebar-item-background--hover); + background: var(--color-background-primary); +} + +button.sphinx-tabs-tab:hover { + background-color: var(--color-sidebar-item-background--hover); +} + +/** Custom classes to fix scrolling in tables by decreasing the + font size or breaking certain columns. + Specify the classes in the Markdown file with, for example: + ```{rst-class} break-col-4 min-width-4-8 + ``` +**/ + +table.dec-font-size { + font-size: smaller; +} +table.break-col-1 td.text-left:first-child { + word-break: break-word; +} +table.break-col-4 td.text-left:nth-child(4) { + word-break: break-word; +} +table.min-width-1-15 td.text-left:first-child { + min-width: 15em; +} +table.min-width-4-8 td.text-left:nth-child(4) { + min-width: 8em; +} + +/** Underline for abbreviations **/ + +abbr[title] { + text-decoration: underline solid #cdcdcd; +} + +/** Use the same style for right-details as for left-details **/ +.bottom-of-page .right-details { + font-size: var(--font-size--small); + display: block; +} + +/** Version switcher */ +button.version_select { + color: var(--color-foreground-primary); + background-color: var(--color-toc-background); + padding: 5px 10px; + border: none; +} + +.version_select:hover, .version_select:focus { + background-color: var(--color-sidebar-item-background--hover); +} + +.version_dropdown { + position: relative; + display: inline-block; + text-align: right; + font-size: var(--sidebar-item-font-size); +} + +.available_versions { + display: none; + position: absolute; + right: 0px; + background-color: var(--color-toc-background); + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 11; +} + +.available_versions a { + color: var(--color-foreground-primary); + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.available_versions a:hover {background-color: var(--color-sidebar-item-background--current)} + +.show {display:block;} + +/** Fix for nested numbered list - the nested list is lettered **/ +ol.arabic ol.arabic { + list-style: lower-alpha; +} + +/** Make expandable sections look like links **/ +details summary { + color: var(--color-link); +} + +/** Fix the styling of the version box for readthedocs **/ + +#furo-readthedocs-versions .rst-versions, #furo-readthedocs-versions .rst-current-version, #furo-readthedocs-versions:focus-within .rst-current-version, #furo-readthedocs-versions:hover .rst-current-version { + background: var(--color-sidebar-item-background--hover); +} + +.rst-versions .rst-other-versions dd a { + color: var(--color-link); +} + +#furo-readthedocs-versions:focus-within .rst-current-version .fa-book, #furo-readthedocs-versions:hover .rst-current-version .fa-book, .rst-versions .rst-other-versions { + color: var(--color-sidebar-link-text); +} + +.rst-versions .rst-current-version { + color: var(--color-version-popup); + font-weight: bolder; +} diff --git a/docs/.sphinx/_static/favicon.png b/docs/.sphinx/_static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c7109908f2af5c9bb0ad130c13ac143929643aa2 GIT binary patch literal 57806 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

&o&1XroP74a!84^ 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

x=_8 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 Tci{bhEwz{WC?{4XqVNA-TM;k( literal 0 HcmV?d00001 diff --git a/docs/.sphinx/_static/github_issue_links.css b/docs/.sphinx/_static/github_issue_links.css new file mode 100644 index 00000000..af4be86c --- /dev/null +++ b/docs/.sphinx/_static/github_issue_links.css @@ -0,0 +1,24 @@ +.github-issue-link-container { + padding-right: 0.5rem; +} +.github-issue-link { + font-size: var(--font-size--small); + font-weight: bold; + background-color: #DD4814; + padding: 13px 23px; + text-decoration: none; +} +.github-issue-link:link { + color: #FFFFFF; +} +.github-issue-link:visited { + color: #FFFFFF +} +.muted-link.github-issue-link:hover { + color: #FFFFFF; + text-decoration: underline; +} +.github-issue-link:active { + color: #FFFFFF; + text-decoration: underline; +} diff --git a/docs/.sphinx/_static/github_issue_links.js b/docs/.sphinx/_static/github_issue_links.js new file mode 100644 index 00000000..980609cd --- /dev/null +++ b/docs/.sphinx/_static/github_issue_links.js @@ -0,0 +1,26 @@ +window.onload = function() { + const link = document.createElement("a"); + link.classList.add("muted-link"); + link.classList.add("github-issue-link"); + link.text = "Give feedback"; + link.href = ( + github_url + + "/issues/new?" + + "title=docs%3A+TYPE+YOUR+QUESTION+HERE" + + "&body=*Please describe the question or issue you're facing with " + + `"${document.title}"` + + ".*" + + "%0A%0A%0A%0A%0A" + + "---" + + "%0A" + + `*Reported+from%3A+${location.href}*` + ); + link.target = "_blank"; + + const div = document.createElement("div"); + div.classList.add("github-issue-link-container"); + div.append(link) + + const container = document.querySelector(".article-container > .content-icon-container"); + container.prepend(div); +}; diff --git a/docs/.sphinx/_static/header-nav.js b/docs/.sphinx/_static/header-nav.js new file mode 100644 index 00000000..3608576e --- /dev/null +++ b/docs/.sphinx/_static/header-nav.js @@ -0,0 +1,10 @@ +$(document).ready(function() { + $(document).on("click", function () { + $(".more-links-dropdown").hide(); + }); + + $('.nav-more-links').click(function(event) { + $('.more-links-dropdown').toggle(); + event.stopPropagation(); + }); +}) diff --git a/docs/.sphinx/_static/header.css b/docs/.sphinx/_static/header.css new file mode 100644 index 00000000..0b944090 --- /dev/null +++ b/docs/.sphinx/_static/header.css @@ -0,0 +1,167 @@ +.p-navigation { + border-bottom: 1px solid var(--color-sidebar-background-border); +} + +.p-navigation__nav { + background: #333333; + display: flex; +} + +.p-logo { + display: flex !important; + padding-top: 0 !important; + text-decoration: none; +} + +.p-logo-image { + height: 44px; + padding-right: 10px; +} + +.p-logo-text { + margin-top: 18px; + color: white; + text-decoration: none; +} + +ul.p-navigation__links { + display: flex; + list-style: none; + margin-left: 0; + margin-top: auto; + margin-bottom: auto; + max-width: 800px; + width: 100%; +} + +ul.p-navigation__links li { + margin: 0 auto; + text-align: center; + width: 100%; +} + +ul.p-navigation__links li a { + background-color: rgba(0, 0, 0, 0); + border: none; + border-radius: 0; + color: var(--color-sidebar-link-text); + display: block; + font-weight: 400; + line-height: 1.5rem; + margin: 0; + overflow: hidden; + padding: 1rem 0; + position: relative; + text-align: left; + text-overflow: ellipsis; + transition-duration: .1s; + transition-property: background-color, color, opacity; + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + white-space: nowrap; + width: 100%; +} + +ul.p-navigation__links .p-navigation__link { + color: #ffffff; + font-weight: 300; + text-align: center; + text-decoration: none; +} + +ul.p-navigation__links .p-navigation__link:hover { + background-color: #2b2b2b; +} + +ul.p-navigation__links .p-dropdown__link:hover { + background-color: var(--color-sidebar-item-background--hover); +} + +ul.p-navigation__links .p-navigation__sub-link { + background: var(--color-background-primary); + padding: .5rem 0 .5rem .5rem; + font-weight: 300; +} + +ul.p-navigation__links .more-links-dropdown li a { + border-left: 1px solid var(--color-sidebar-background-border); + border-right: 1px solid var(--color-sidebar-background-border); +} + +ul.p-navigation__links .more-links-dropdown li:first-child a { + border-top: 1px solid var(--color-sidebar-background-border); +} + +ul.p-navigation__links .more-links-dropdown li:last-child a { + border-bottom: 1px solid var(--color-sidebar-background-border); +} + +ul.p-navigation__links .p-navigation__logo { + padding: 0.5rem; +} + +ul.p-navigation__links .p-navigation__logo img { + width: 40px; +} + +ul.more-links-dropdown { + display: none; + overflow-x: visible; + height: 0; + z-index: 55; + padding: 0; + position: relative; + list-style: none; + margin-bottom: 0; + margin-top: 0; +} + +.nav-more-links::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23111' d='M8.187 11.748l6.187-6.187-1.06-1.061-5.127 5.127L3.061 4.5 2 5.561z'/%3E%3C/svg%3E"); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + content: ""; + display: block; + filter: invert(100%); + height: 1rem; + pointer-events: none; + position: absolute; + right: 1rem; + text-indent: calc(100% + 10rem); + top: calc(1rem + 0.25rem); + width: 1rem; +} + +.nav-ubuntu-com { + display: none; +} + +@media only screen and (min-width: 480px) { + ul.p-navigation__links li { + width: 100%; + } + + .nav-ubuntu-com { + display: inherit; + } +} + +@media only screen and (max-width: 800px) { + .nav-more-links { + margin-left: auto !important; + padding-right: 2rem !important; + width: 8rem !important; + } +} + +@media only screen and (min-width: 800px) { + ul.p-navigation__links li { + width: 100% !important; + } +} + +@media only screen and (min-width: 1310px) { + ul.p-navigation__links { + margin-left: calc(50% - 41em); + } +} diff --git a/docs/.sphinx/_static/tag.png b/docs/.sphinx/_static/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f6e5aa4bc55fb934c973726b10a0efc92445a8 GIT binary patch literal 6781 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 literal 0 HcmV?d00001 diff --git a/docs/.sphinx/_templates/base.html b/docs/.sphinx/_templates/base.html new file mode 100644 index 00000000..62ffe6b8 --- /dev/null +++ b/docs/.sphinx/_templates/base.html @@ -0,0 +1,7 @@ +{% extends "furo/base.html" %} + +{% block theme_scripts %} + +{% endblock theme_scripts %} diff --git a/docs/.sphinx/_templates/footer.html b/docs/.sphinx/_templates/footer.html new file mode 100644 index 00000000..75944868 --- /dev/null +++ b/docs/.sphinx/_templates/footer.html @@ -0,0 +1,90 @@ +{# ru-fu: copied from Furo, with modifications as stated below #} + +

+
+
+ {%- if show_copyright %} + + {%- endif %} + + {# ru-fu: removed "Made with" #} + + {%- if last_updated -%} +
+ {% trans last_updated=last_updated|e -%} + Last updated on {{ last_updated }} + {%- endtrans -%} +
+ {%- endif %} + + {%- if show_source and has_source and sourcename %} + + {%- endif %} +
+
+ + {# ru-fu: replaced RTD icons with our links #} + + {% if discourse %} + + {% endif %} + + {% if github_url and github_version and github_folder %} + + {% if github_issues %} + + {% endif %} + + + {% endif %} + + +
+
+ diff --git a/docs/.sphinx/_templates/header.html b/docs/.sphinx/_templates/header.html new file mode 100644 index 00000000..1a128b6f --- /dev/null +++ b/docs/.sphinx/_templates/header.html @@ -0,0 +1,36 @@ + diff --git a/docs/.sphinx/_templates/page.html b/docs/.sphinx/_templates/page.html new file mode 100644 index 00000000..bda30610 --- /dev/null +++ b/docs/.sphinx/_templates/page.html @@ -0,0 +1,49 @@ +{% extends "furo/page.html" %} + +{% block footer %} + {% include "footer.html" %} +{% endblock footer %} + +{% block body -%} + {% include "header.html" %} + {{ super() }} +{%- endblock body %} + +{% if meta and ((meta.discourse and discourse_prefix) or meta.relatedlinks) %} + {% set furo_hide_toc_orig = furo_hide_toc %} + {% set furo_hide_toc=false %} +{% endif %} + +{% block right_sidebar %} +
+ {% if not furo_hide_toc_orig %} +
+ + {{ _("Contents") }} + +
+
+
+ {{ toc }} +
+
+ {% endif %} + {% if meta and ((meta.discourse and discourse_prefix) or meta.relatedlinks) %} + + + {% endif %} +
+{% endblock right_sidebar %} diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt new file mode 100644 index 00000000..66fc4e97 --- /dev/null +++ b/docs/.sphinx/pinned-requirements.txt @@ -0,0 +1,57 @@ +alabaster==0.7.13 +Babel==2.12.1 +beautifulsoup4==4.11.2 +bracex==2.3.post1 +certifi==2022.12.7 +charset-normalizer==3.1.0 +colorama==0.4.6 +contourpy==1.0.7 +cycler==0.11.0 +docutils==0.19 +fonttools==4.39.0 +furo==2022.12.7 +html5lib==1.1 +idna==3.4 +imagesize==1.4.1 +importlib-metadata==6.0.0 +importlib-resources==5.12.0 +Jinja2==3.1.2 +kiwisolver==1.4.4 +livereload==2.6.3 +lxd-sphinx-extensions==0.0.6 +lxml==4.9.2 +Markdown==3.4.1 +MarkupSafe==2.1.2 +matplotlib==3.7.1 +numpy==1.24.2 +packaging==23.0 +Pillow==9.4.0 +Pygments==2.14.0 +pyparsing==3.0.9 +pyspelling==2.8.2 +python-dateutil==2.8.2 +pytz==2022.7.1 +PyYAML==6.0 +requests==2.28.2 +six==1.16.0 +snowballstemmer==2.2.0 +soupsieve==2.4 +sphinx==6.1.3 +sphinx-autobuild==2021.3.14 +sphinx-basic-ng==1.0.0b1 +sphinx-copybutton==0.5.1 +sphinx-design==0.3.0 +sphinx-reredirects==0.1.1 +sphinx-tabs==3.4.1 +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +sphinxext-opengraph==0.8.1 +tornado==6.2 +urllib3==1.26.14 +wcmatch==8.4.1 +webencodings==0.5.1 +zipp==3.15.0 diff --git a/docs/.sphinx/requirements.txt b/docs/.sphinx/requirements.txt new file mode 100644 index 00000000..d6f37e04 --- /dev/null +++ b/docs/.sphinx/requirements.txt @@ -0,0 +1,13 @@ +sphinx +sphinx-autobuild +sphinx-design +furo +sphinx-tabs +sphinx-reredirects +pyspelling +sphinxext-opengraph +lxd-sphinx-extensions +sphinx-copybutton +myst-parser +sphinxcontrib-jquery +sphinx-notfound-page diff --git a/docs/.sphinx/spellingcheck.yaml b/docs/.sphinx/spellingcheck.yaml new file mode 100644 index 00000000..ebef2081 --- /dev/null +++ b/docs/.sphinx/spellingcheck.yaml @@ -0,0 +1,27 @@ +matrix: +- name: rST files + aspell: + lang: en + d: en_GB + dictionary: + wordlists: + - .wordlist.txt + output: .sphinx/.wordlist.dic + sources: + - _build/**/*.html + pipeline: + - pyspelling.filters.html: + comments: false + attributes: + - title + - alt + ignores: + - code + - pre + - spellexception + - link + - title + - div.relatedlinks + - div.visually-hidden + - img + - a.p-navigation__link diff --git a/docs/.wokeignore b/docs/.wokeignore new file mode 100644 index 00000000..c64a6037 --- /dev/null +++ b/docs/.wokeignore @@ -0,0 +1,4 @@ +# the cheat sheets contain a link to a repository with a block word which we +# cannot avoid for now, ie +# https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html +doc-cheat-sheet* diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt new file mode 100644 index 00000000..212ce5b5 --- /dev/null +++ b/docs/.wordlist.txt @@ -0,0 +1,17 @@ +Diátaxis +favicon +https +installable +Makefile +MyST +Permalink +ReadMe +readthedocs +reST +reStructuredText +RTD +subdirectories +subtree +txt +UI +yaml diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..dcf383a4 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,65 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build +VENV = .sphinx/venv/bin/activate + + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +install: + @echo "... setting up virtualenv" + python3 -m venv .sphinx/venv + . $(VENV); pip install --upgrade -r .sphinx/requirements.txt + + @echo "\n" \ + "--------------------------------------------------------------- \n" \ + "* watch, build and serve the documentation: make run \n" \ + "* only build: make html \n" \ + "* only serve: make serve \n" \ + "* clean built doc files: make clean-doc \n" \ + "* clean full environment: make clean \n" \ + "* check spelling: make spelling \n" \ + "* check inclusive language: make woke \n" \ + "--------------------------------------------------------------- \n" +run: + . $(VENV); sphinx-autobuild -c . -b dirhtml "$(SOURCEDIR)" "$(BUILDDIR)" + +html: + . $(VENV); $(SPHINXBUILD) -c . -b dirhtml "$(SOURCEDIR)" "$(BUILDDIR)" -w .sphinx/warnings.txt + +epub: + . $(VENV); $(SPHINXBUILD) -c . -b epub "$(SOURCEDIR)" "$(BUILDDIR)" -w .sphinx/warnings.txt + +serve: + cd "$(BUILDDIR)"; python3 -m http.server 8000 + +clean: clean-doc + rm -rf .sphinx/venv + +clean-doc: + git clean -fx "$(BUILDDIR)" + +spelling: html + . $(VENV) ; python3 -m pyspelling -c .sphinx/spellingcheck.yaml + +linkcheck: + . $(VENV) ; $(SPHINXBUILD) -c . -b linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" + +woke: + type woke >/dev/null 2>&1 || { sudo snap install woke; } + woke *.rst **/*.rst -c https://github.com/canonical-web-and-design/Inclusive-naming/raw/main/config.yml + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + . $(VENV); $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..fa20cf16 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,177 @@ +import sys + +sys.path.append('./') +from custom_conf import * + +# Configuration file for the Sphinx documentation builder. +# You should not do any modifications to this file. Put your custom +# configuration into the custom_conf.py file. +# If you need to change this file, contribute the changes upstream. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +############################################################ +### Extensions +############################################################ + +extensions = [ + 'sphinx_design', + 'sphinx_tabs.tabs', + 'sphinx_reredirects', + 'youtube-links', + 'related-links', + 'custom-rst-roles', + 'terminal-output', + 'sphinx_copybutton', + 'sphinxext.opengraph', + 'myst_parser', + 'sphinxcontrib.jquery', + 'notfound.extension' +] +extensions.extend(custom_extensions) + +### Configuration for extensions + +# Additional MyST syntax +myst_enable_extensions = [ + 'substitution', + 'deflist' +] + +# Used for related links +if 'discourse' in html_context: + html_context['discourse_prefix'] = html_context['discourse'] + '/t/' + +# The default for notfound_urls_prefix usually works, but not for +# documentation on documentation.ubuntu.com +if slug: + notfound_urls_prefix = '/' + slug + '/en/latest/' + +notfound_context = { + 'title': 'Page not found', + 'body': '

Page not found

\n\n

Sorry, but the documentation page that you are looking for was not found.

\n

Documentation changes over time, and pages are moved around. We try to redirect you to the updated content where possible, but unfortunately, that didn\'t work this time (maybe because the content you were looking for does not exist in this version of the documentation).

\n

You can try to use the navigation to locate the content you\'re looking for, or search for a similar page.

\n', +} + +# Default image for OGP (to prevent font errors, see +# https://github.com/canonical/sphinx-docs-starter-pack/pull/54 ) +if not 'ogp_image' in locals(): + ogp_image = 'https://assets.ubuntu.com/v1/253da317-image-document-ubuntudocs.svg' + +############################################################ +### General configuration +############################################################ + +exclude_patterns = [ + '_build', + 'Thumbs.db', + '.DS_Store', + '.sphinx', + 'doc-cheat-sheet*', +] +exclude_patterns.extend(custom_excludes) + +rst_epilog = ''' +.. include:: /reuse/links.txt +''' +if 'custom_rst_epilog' in locals(): + rst_epilog = custom_rst_epilog + +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +if not 'conf_py_path' in html_context and 'github_folder' in html_context: + html_context['conf_py_path'] = html_context['github_folder'] + +############################################################ +### Styling +############################################################ + +# Find the current builder +builder = 'dirhtml' +if '-b' in sys.argv: + builder = sys.argv[sys.argv.index('-b')+1] + +# Setting templates_path for epub makes the build fail +if builder == 'dirhtml' or builder == 'html': + templates_path = ['.sphinx/_templates'] + +# Theme configuration +html_theme = 'furo' +html_last_updated_fmt = '' +html_permalinks_icon = '¶' +html_theme_options = { + 'light_css_variables': { + 'font-stack': 'Ubuntu, -apple-system, Segoe UI, Roboto, Oxygen, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif', + 'font-stack--monospace': 'Ubuntu Mono, Consolas, Monaco, Courier, monospace', + 'color-foreground-primary': '#111', + 'color-foreground-secondary': 'var(--color-foreground-primary)', + 'color-foreground-muted': '#333', + 'color-background-secondary': '#FFF', + 'color-background-hover': '#f2f2f2', + 'color-brand-primary': '#111', + 'color-brand-content': '#06C', + 'color-api-background': '#cdcdcd', + 'color-inline-code-background': 'rgba(0,0,0,.03)', + 'color-sidebar-link-text': '#111', + 'color-sidebar-item-background--current': '#ebebeb', + 'color-sidebar-item-background--hover': '#f2f2f2', + 'toc-font-size': 'var(--font-size--small)', + 'color-admonition-title-background--note': 'var(--color-background-primary)', + 'color-admonition-title-background--tip': 'var(--color-background-primary)', + 'color-admonition-title-background--important': 'var(--color-background-primary)', + 'color-admonition-title-background--caution': 'var(--color-background-primary)', + 'color-admonition-title--note': '#24598F', + 'color-admonition-title--tip': '#24598F', + 'color-admonition-title--important': '#C7162B', + 'color-admonition-title--caution': '#F99B11', + 'color-highlighted-background': '#EbEbEb', + 'color-link-underline': 'var(--color-background-primary)', + 'color-link-underline--hover': 'var(--color-background-primary)', + 'color-version-popup': '#772953' + }, + 'dark_css_variables': { + 'color-foreground-secondary': 'var(--color-foreground-primary)', + 'color-foreground-muted': '#CDCDCD', + 'color-background-secondary': 'var(--color-background-primary)', + 'color-background-hover': '#666', + 'color-brand-primary': '#fff', + 'color-brand-content': '#06C', + 'color-sidebar-link-text': '#f7f7f7', + 'color-sidebar-item-background--current': '#666', + 'color-sidebar-item-background--hover': '#333', + 'color-admonition-background': 'transparent', + 'color-admonition-title-background--note': 'var(--color-background-primary)', + 'color-admonition-title-background--tip': 'var(--color-background-primary)', + 'color-admonition-title-background--important': 'var(--color-background-primary)', + 'color-admonition-title-background--caution': 'var(--color-background-primary)', + 'color-admonition-title--note': '#24598F', + 'color-admonition-title--tip': '#24598F', + 'color-admonition-title--important': '#C7162B', + 'color-admonition-title--caution': '#F99B11', + 'color-highlighted-background': '#666', + 'color-link-underline': 'var(--color-background-primary)', + 'color-link-underline--hover': 'var(--color-background-primary)', + 'color-version-popup': '#F29879' + }, +} + +############################################################ +### Additional files +############################################################ + +html_static_path = ['.sphinx/_static'] + +html_css_files = [ + 'custom.css', + 'header.css', + 'github_issue_links.css', +] +html_css_files.extend(custom_html_css_files) + +html_js_files = ['header-nav.js'] +if 'github_issues' in html_context and html_context['github_issues'] and not disable_feedback_button: + html_js_files.append('github_issue_links.js') +html_js_files.extend(custom_html_js_files) diff --git a/docs/custom_conf.py b/docs/custom_conf.py new file mode 100644 index 00000000..fcfe2bc7 --- /dev/null +++ b/docs/custom_conf.py @@ -0,0 +1,126 @@ +import datetime + +# Custom configuration for the Sphinx documentation builder. +# All configuration specific to your project should be done in this file. +# +# The file is included in the common conf.py configuration file. +# You can modify any of the settings below or add any configuration that +# is not covered by the common conf.py file. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +############################################################ +### Project information +############################################################ + +# Product name +project = 'Documentation starter pack' +author = 'Canonical Group Ltd' + +# Uncomment if your product uses release numbers +# release = '1.0' + +# The default value uses the current year as the copyright year +copyright = '%s, %s' % (datetime.date.today().year, author) + +## Open Graph configuration - defines what is displayed in the website preview +# The URL of the documentation output +ogp_site_url = 'https://canonical-starter-pack.readthedocs-hosted.com/' +# The documentation website name (usually the same as the product name) +ogp_site_name = project +# An image or logo that is used in the preview +ogp_image = 'https://assets.ubuntu.com/v1/253da317-image-document-ubuntudocs.svg' + +# Update with the favicon for your product (default is the circle of friends) +html_favicon = '.sphinx/_static/favicon.png' + +# (Some settings must be part of the html_context dictionary, while others +# are on root level. Don't move the settings.) +html_context = { + + # Change to the link to your product website (without "https://") + 'product_page': 'documentation.ubuntu.com', + + # Add your product tag to ".sphinx/_static" and change the path + # here (start with "_static"), default is the circle of friends + 'product_tag': '_static/tag.png', + + # Change to the discourse instance you want to be able to link to + # using the :discourse: metadata at the top of a file + # (use an empty value if you don't want to link) + 'discourse': 'https://discourse.ubuntu.com', + + # Change to the GitHub info for your project + 'github_url': 'https://github.com/canonical/starter-pack', + + # Change to the branch for this version of the documentation + 'github_version': 'main', + + # Change to the folder that contains the documentation + # (usually "/" or "/docs/") + 'github_folder': '/', + + # Change to an empty value if your GitHub repo doesn't have issues enabled. + # This will disable the feedback button and the issue link in the footer. + 'github_issues': 'enabled' +} + +# If your project is on documentation.ubuntu.com, specify the project +# slug (for example, "lxd") here. +slug = "" + +############################################################ +### Redirects +############################################################ + +# Set up redirects (https://documatt.gitlab.io/sphinx-reredirects/usage.html) +# For example: 'explanation/old-name.html': '../how-to/prettify.html', + +redirects = {} + +############################################################ +### Link checker exceptions +############################################################ + +# Links to ignore when checking links + +linkcheck_ignore = [ + 'http://127.0.0.1:8000' + ] + +############################################################ +### Additions to default configuration +############################################################ + +## The following settings are appended to the default configuration. +## Use them to extend the default functionality. + +# Add extensions +custom_extensions = [] + +# Add files or directories that should be excluded from processing. +custom_excludes = [] + +# Add CSS files (located in .sphinx/_static/) +custom_html_css_files = [] + +# Add JavaScript files (located in .sphinx/_static/) +custom_html_js_files = [] + +## The following settings override the default configuration. + +# Specify a reST string that is included at the end of each file. +# If commented out, use the default (which pulls the reuse/links.txt +# file into each reST file). +# custom_rst_epilog = '' + +# By default, the documentation includes a feedback button at the top. +# You can disable it by setting the following configuration to True. +disable_feedback_button = False + +############################################################ +### Additional configuration +############################################################ + +## Add any configuration that is not covered by the common conf.py file. diff --git a/docs/doc-cheat-sheet-myst.md b/docs/doc-cheat-sheet-myst.md new file mode 100644 index 00000000..19b07f53 --- /dev/null +++ b/docs/doc-cheat-sheet-myst.md @@ -0,0 +1,243 @@ +--- +orphan: true +myst: + substitutions: + reuse_key: "This is **included** text." + advanced_reuse_key: "This is a substitution that includes a code block: + ``` + code block + ```" +--- + +(cheat-sheet-myst)= +# Markdown/MyST cheat sheet + +This file contains the syntax for commonly used Markdown and MyST markup. +Open it in your text editor to quickly copy and paste the markup you need. + +Also see the [MyST documentation](https://myst-parser.readthedocs.io/en/latest/index.html) for detailed information, and the [Canonical Documentation Style Guide](https://docs.ubuntu.com/styleguide/en) for general style conventions. + +## H2 heading + +### H3 heading + +#### H4 heading + +##### H5 heading + +## Inline formatting + +- {guilabel}`UI element` +- `code` +- {command}`command` +- {kbd}`Key` +- *Italic* +- **Bold** + +## Code blocks + +Start a code block: + + code: + - example: true + +``` +# Demonstrate a code block +code: + - example: true +``` + +```yaml +# Demonstrate a code block +code: + - example: true +``` + +(_a_section_target)= +## Links + +- [Canonical website](https://canonical.com/) +- https://canonical.com/ +- {ref}`a_section_target` +- {ref}`Link text ` +- {doc}`index` +- {doc}`Link text ` + + +## Navigation + +Use the following syntax:: + + ```{toctree} + :hidden: + + sub-page1 + sub-page2 + ``` + +## Lists + +1. Step 1 + - Item 1 + * Sub-item + - Item 2 + 1. Sub-step 1 + 1. Sub-step 2 +1. Step 2 + 1. Sub-step 1 + - Item + 1. Sub-step 2 + +Term 1 +: Definition + +Term 2 +: Definition + +## Tables + +## Markdown tables + +| Header 1 | Header 2 | +|------------------------------------|----------| +| Cell 1
Second paragraph | Cell 2 | +| Cell 3 | Cell 4 | + +Centred: + +| Header 1 | Header 2 | +|:----------------------------------:|:--------:| +| Cell 1
Second paragraph | Cell 2 | +| Cell 3 | Cell 4 | + +## List tables + +```{list-table} + :header-rows: 1 + +* - Header 1 + - Header 2 +* - Cell 1 + + Second paragraph + - Cell 2 +* - Cell 3 + - Cell 4 +``` + +Centred: + +```{list-table} + :header-rows: 1 + :align: center + +* - Header 1 + - Header 2 +* - Cell 1 + + Second paragraph + - Cell 2 +* - Cell 3 + - Cell 4 +``` + +## Notes + +```{note} +A note. +``` + +```{tip} +A tip. +``` + +```{important} +Important information +``` + +```{caution} +This might damage your hardware! +``` + +## Images + +![Alt text](https://assets.ubuntu.com/v1/b3b72cb2-canonical-logo-166.png) + +```{figure} https://assets.ubuntu.com/v1/b3b72cb2-canonical-logo-166.png + :width: 100px + :alt: Alt text + + Figure caption +``` + +## Reuse + +### Keys + +Keys can be defined at the top of a file, or in a `myst_substitutions` option in `conf.py`. + +{{reuse_key}} + +{{advanced_reuse_key}} + +### File inclusion + +```{include} index.rst + :start-after: include_start + :end-before: include_end +``` + +## Tabs + +````{tabs} +```{group-tab} Tab 1 + +Content Tab 1 +``` + +```{group-tab} Tab 2 +Content Tab 2 +``` +```` + +## Glossary + +```{glossary} + +some term + Definition of the example term. +``` + +{term}`some term` + +## More useful markup + +- ```{versionadded} X.Y +- {abbr}`API (Application Programming Interface)` + +---- + +## Custom extensions + +Related links at the top of the page (surrounded by `---`): + + relatedlinks: https://github.com/canonical/lxd-sphinx-extensions, [RTFM](https://www.google.com) + discourse: 12345 + +Terms that should not be checked by the spelling checker: {spellexception}`PurposelyWrong` + +A terminal view with input and output: + +```{terminal} + :input: command + :user: root + :host: vampyr + +the output +``` + +A link to a YouTube video: + +```{youtube} https://www.youtube.com/watch?v=iMLiK1fX4I0 + :title: Demo +``` diff --git a/docs/doc-cheat-sheet.rst b/docs/doc-cheat-sheet.rst new file mode 100644 index 00000000..b12ebcdf --- /dev/null +++ b/docs/doc-cheat-sheet.rst @@ -0,0 +1,258 @@ +:orphan: + +.. _cheat-sheet: + +reStructuredText cheat sheet +============================ + +This file contains the syntax for commonly used reST markup. +Open it in your text editor to quickly copy and paste the markup you need. + +See the `reStructuredText style guide `_ for detailed information and conventions. + +Also see the `Sphinx reStructuredText Primer `_ for more details on reST, and the `Canonical Documentation Style Guide `_ for general style conventions. + +H2 heading +---------- + +H3 heading +~~~~~~~~~~ + +H4 heading +^^^^^^^^^^ + +H5 heading +.......... + +Inline formatting +----------------- + +- :guilabel:`UI element` +- ``code`` +- :file:`file path` +- :command:`command` +- :kbd:`Key` +- *Italic* +- **Bold** + +Code blocks +----------- + +Start a code block:: + + code: + - example: true + +.. code:: + + # Demonstrate a code block + code: + - example: true + +.. code:: yaml + + # Demonstrate a code block + code: + - example: true + +.. _a_section_target: + +Links +----- + +- `Canonical website `_ +- `Canonical website`_ (defined in ``reuse/links.txt`` or at the bottom of the page) +- https:\ //canonical.com/ +- :ref:`a_section_target` +- :ref:`Link text ` +- :doc:`index` +- :doc:`Link text ` + + +Navigation +---------- + +Use the following syntax:: + + .. toctree:: + :hidden: + + sub-page1 + sub-page2 + + +Lists +----- + +1. Step 1 + + - Item 1 + + * Sub-item + - Item 2 + + i. Sub-step 1 + #. Sub-step 2 +#. Step 2 + + a. Sub-step 1 + + - Item + #. Sub-step 2 + +Term 1: + Definition +Term 2: + Definition + +Tables +------ + ++----------------------+------------+ +| Header 1 | Header 2 | ++======================+============+ +| Cell 1 | Cell 2 | +| | | +| Second paragraph | | ++----------------------+------------+ +| Cell 3 | Cell 4 | ++----------------------+------------+ + +.. list-table:: + :header-rows: 1 + + * - Header 1 + - Header 2 + * - Cell 1 + + Second paragraph + - Cell 2 + * - Cell 3 + - Cell 4 + +.. rst-class:: align-center + + +----------------------+------------+ + | Header 1 | Header 2 | + +======================+============+ + | Cell 1 | Cell 2 | + | | | + | Second paragraph | | + +----------------------+------------+ + | Cell 3 | Cell 4 | + +----------------------+------------+ + +.. list-table:: + :header-rows: 1 + :align: center + + * - Header 1 + - Header 2 + * - Cell 1 + + Second paragraph + - Cell 2 + * - Cell 3 + - Cell 4 + +Notes +----- + +.. note:: + A note. + +.. tip:: + A tip. + +.. important:: + Important information + +.. caution:: + This might damage your hardware! + +Images +------ + +.. image:: https://assets.ubuntu.com/v1/b3b72cb2-canonical-logo-166.png + +.. figure:: https://assets.ubuntu.com/v1/b3b72cb2-canonical-logo-166.png + :width: 100px + :alt: Alt text + + Figure caption + +Reuse +----- + +.. |reuse_key| replace:: This is **included** text. + +|reuse_key| + +.. include:: index.rst + :start-after: include_start + :end-before: include_end + +Tabs +---- + +.. tabs:: + + .. group-tab:: Tab 1 + + Content Tab 1 + + .. group-tab:: Tab 2 + + Content Tab 2 + + +Glossary +-------- + +.. glossary:: + + example term + Definition of the example term. + +:term:`example term` + +More useful markup +------------------ + +- .. versionadded:: X.Y +- | Line 1 + | Line 2 + | Line 3 +- .. This is a comment +- :abbr:`API (Application Programming Interface)` + +---- + +Custom extensions +----------------- + +Related links at the top of the page:: + + :relatedlinks: https://github.com/canonical/lxd-sphinx-extensions, [RTFM](https://www.google.com) + :discourse: 12345 + +Terms that should not be checked by the spelling checker: :spellexception:`PurposelyWrong` + +A terminal view with input and output: + +.. terminal:: + :input: command + :user: root + :host: vampyr + + the output + +A link to a YouTube video: + +.. youtube:: https://www.youtube.com/watch?v=iMLiK1fX4I0 + :title: Demo + + + +.. LINKS +.. _Canonical website: https://canonical.com/ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..317591c5 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +Starter pack +============ + +This starter pack contains the files you need to quickly set up your Sphinx documentation. + +Before you start, see the information about `Read the Docs at Canonical`_ and the instructions for `How to publish documentation on Read the Docs`_. + +Then, to start setting up your docs, check the :doc:`ReadMe ` for instructions. +The `Example product documentation`_ shows how to set up a Diátaxis structure in Sphinx. + +For quick help on reST or MyST syntax, see the :file:`doc-cheat-sheet.rst.txt` or :file:`doc-cheat-sheet-myst.md.txt` files in the repository. +(Open the files in your text editor; the rendered output is not very useful.) + +More information is available in the `reStructuredText style guide`_. + +.. toctree:: + :hidden: + :maxdepth: 2 + + ReadMe diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 00000000..228535f0 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1,236 @@ +Documentation starter pack +========================== + +See the `Sphinx and Read the Docs `_ guide for instructions on how to get started with Sphinx documentation. + +Then go through the following sections to use this starter pack to set up your docs repository. + +Set up your documentation repository +------------------------------------ + +You can either create a standalone documentation project based on this repository or include the files from this repository in a dedicated documentation folder in an existing code repository. + +**Note:** We're planning to provide the contents of this repository as an installable package in the future, but currently, you need to copy and update the required files manually. + +Standalone documentation repository +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To create a standalone documentation repository, clone this starter pack +repository, `update the configuration <#configure-the-documentation>`_, and +then commit all files to the documentation repository. + +You don't need to move any files, and you don't need to do any special +configuration on Read the Docs. + +Here is one way to do this for newly-created fictional docs repository +``canonical/alpha-docs``: + +.. code-block:: none + + git clone git@github.com:canonical/sphinx-docs-starter-pack alpha-docs + cd alpha-docs + rm -rf .git + git init + git branch -m main + UPDATE THE CONFIGURATION AND BUILD THE DOCS + git add -A + git commit -m "Import sphinx-docs-starter-pack" + git remote add upstream git@github.com:canonical/alpha-docs + git push -f upstream main + +Documentation in a code repository +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To add documentation to an existing code repository: + +#. create a directory called ``docs`` at the root of the code repository +#. populate the above directory with the contents of the starter pack + repository (with the exception of the ``.git`` directory) +#. copy the file(s) located in the ``docs/.github/workflows`` directory into + the code repository's ``.github/workflows`` directory +#. in the above workflow file(s), set the values of the ``working-directory`` + and ``workdir`` fields to ``docs`` +#. in file ``docs/.readthedocs.yaml`` set the following: + + * ``configuration: docs/conf.py`` + * ``requirements: docs/.sphinx/requirements.txt`` + +**Note:** When configuring RTD itself for your project, the setting **Path for +.readthedocs.yaml** (under **Advanced Settings**) will need to be given the +value of "docs/.readthedocs.yaml". + +Getting started +--------------- + +There are make targets defined in the ``Makefile`` that do various things. To +get started, we will: + +* install prerequisite software +* view the documentation + +Install prerequisite software +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To install the prerequisites: + +.. code-block:: none + + make install + +This will create a virtual environment (``.sphinx/venv``) and install +dependency software (``.sphinx/requirements.txt``) within it. + +A complete set of pinned, known-working dependencies is included in +``.sphinx/pinned-requirements.txt``. + +View the documentation +~~~~~~~~~~~~~~~~~~~~~~ + +To view the documentation: + +.. code-block:: none + + make run + +This will do several things: + +* activate the virtual environment +* build the documentation +* serve the documentation on **127.0.0.1:8000** +* rebuild the documentation each time a file is saved +* send a reload page signal to the browser when the documentation is rebuilt + +The ``run`` target is therefore very convenient when preparing to submit a +change to the documentation. + +Submit your change +~~~~~~~~~~~~~~~~~~ + +Prior to submitting your change, it is recommended to do a fresh build in order +to surface any errors that may cause build issues on the RTD side: + +.. code-block:: none + + make clean-doc + make html + +Configure the documentation +--------------------------- + +You must modify some of the default configuration to suit your project. +To simplify keeping your documentation in sync with the starter pack, all custom configuration is located in the ``custom_conf.py`` file. +You should never modify the common ``conf.py`` file. + +Go through all settings in the ``Project information`` section of the ``custom_conf.py`` file and update them for your project. + +See the following sections for further customisation. + +Configure the header +~~~~~~~~~~~~~~~~~~~~ + +By default, the header contains your product tag, product name (taken from the ``project`` setting in the ``custom_conf.py`` file), a link to your product page, and a drop-down menu for "More resources" that contains links to Discourse and GitHub. + +You can change any of those links or add further links to the "More resources" drop-down by editing the ``.sphinx/_templates/header.html`` file. +For example, you might want to add links to announcements, tutorials, getting started guides, or videos that are not part of the documentation. + +Configure the spelling check +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your documentation uses US English instead of UK English, change this in the +``.sphinx/spellingcheck.yaml`` file. + +To add exceptions for words the spelling check marks as wrong even though they are correct, edit the ``.wordlist.txt`` file. + +Configure the inclusive-language check +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you can't avoid non-inclusive language in some cases, you'll need to +configure exemptions for them. + +In-file exemptions +^^^^^^^^^^^^^^^^^^ + +Suppose a reST file has a link to some site you don't control, and the address +contains "\m\a\s\t\e\r" --- a non-inclusive word. You can't change the link, +but the remainder of the file must be checked for inclusive language. Here the +``woke`` tool's `next-line ignore +`_ feature is +useful, as follows. + +If the link is in-line, move the definition to a line of its own (e.g. among +``.. LINKS`` at the bottom of the file). Above the definition, invoke the +``wokeignore`` rule for the offending word: + +.. code-block:: ReST + + .. LINKS + .. wokeignore:rule=master + .. _link anchor: https://some-external-site.io/master/some-page.html + +Exempt an entire file +^^^^^^^^^^^^^^^^^^^^^ + +If it's necessary *and safe*, you can exempt a whole file from +inclusive-language checks. To exempt ``docs/foo/bar.rst`` for example, add the +following line to ``.wokeignore``: + +.. code-block:: none + + foo/bar.rst + +.. note:: + + For ``.wokeignore`` to take effect, you must also move it into your + project's root directory. If you leave it in ``docs/``, the ``woke`` tool + won't find it and no files will be exempt. + +Change checked file-types and locations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, only reST files are checked for inclusive language --- and only +those in ``docs/`` and its subdirectories. To check Markdown files for example, +or files outside the ``docs/`` subtree, you must change how the ``woke`` tool +is invoked. + +The ``woke`` command appears twice: in the ``docs/Makefile`` and in your +project's ``.github/workflows/automatic-doc-checks.yml`` file. The command +syntax is out-of-scope here --- consult the `woke User Guide +`_. + +Configure the link check +~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have links in the documentation that you don't want to be checked (for +example, because they are local links or give random errors even though they +work), you can add them to the ``linkcheck_ignore`` variable in the ``custom_conf.py`` file. + +Activate/deactivate feedback button +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A feedback button is included by default, which appears at the top of each page +in the documentation. It redirects users to your GitHub issues page, and +populates an issue for them with details of the page they were on when they +clicked the button. + +If your project does not use GitHub issues, set the ``github_issues`` variable +in the ``custom_conf.py`` file to an empty value to disable both the feedback button +and the issue link in the footer. +If you want to deactivate only the feedback button, but keep the link in the +footer, set ``disable_feedback_button`` in the ``custom_conf.py`` file to ``True``. + +Add redirects +~~~~~~~~~~~~~ + +You can add redirects to make sure existing links and bookmarks continue working when you move files around. +To do so, specify the old and new paths in the ``redirects`` setting of the ``custom_conf.py`` file. + +Add custom configuration +~~~~~~~~~~~~~~~~~~~~~~~~ + +To add custom configurations for your project, see the ``Additions to default configuration`` and ``Additional configuration`` sections in the ``custom_conf.py`` file. +These can be used to extend or override the common configuration, or to define additional configuration that is not covered by the common ``conf.py`` file. + +Change log +---------- + +See the `change log `_ for a list of relevant changes to the starter pack. diff --git a/docs/reuse/links.txt b/docs/reuse/links.txt new file mode 100644 index 00000000..04cfff56 --- /dev/null +++ b/docs/reuse/links.txt @@ -0,0 +1,4 @@ +.. _reStructuredText style guide: https://canonical-documentation-with-sphinx-and-readthedocscom.readthedocs-hosted.com/style-guide/ +.. _Read the Docs at Canonical: https://library.canonical.com/documentation/read-the-docs +.. _How to publish documentation on Read the Docs: https://library.canonical.com/documentation/publish-on-read-the-docs +.. _Example product documentation: https://canonical-example-product-documentation.readthedocs-hosted.com/ From 0ffeeb3ba77e1042aa503d6ab3d686b05958f3e2 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 16 Aug 2023 11:39:19 +0100 Subject: [PATCH 02/64] Project name --- docs/custom_conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/custom_conf.py b/docs/custom_conf.py index fcfe2bc7..1bf7e284 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -15,7 +15,7 @@ ############################################################ # Product name -project = 'Documentation starter pack' +project = 'MLflow Documentation' author = 'Canonical Group Ltd' # Uncomment if your product uses release numbers From 786a1c8e6ea10e7543b107ba757f64b87a6ecbf8 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 16 Aug 2023 11:39:44 +0100 Subject: [PATCH 03/64] Doc check refactor Using reusable workflow instead --- .github/workflows/automatic-doc-checks.yml | 64 ++----------------- .../workflows/automatic-doc-checks.yml | 64 ++----------------- 2 files changed, 10 insertions(+), 118 deletions(-) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 0c197638..6264eb8d 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -1,65 +1,11 @@ -name: Automatic documentation checks +name: Main Documentation Checks on: - push - pull_request -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: - spellcheck: - name: Spelling check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install Aspell - run: | - sudo apt-get install aspell aspell-en - - - name: Install the doc framework - working-directory: docs - run: | - make install - - - name: Build docs and run spelling checker - working-directory: docs - run: | - make spelling - - woke: - name: Inclusive language check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install the doc framework - working-directory: docs - run: | - make install - - - name: Run incluse-language checker - working-directory: docs - run: | - make woke - - linkcheck: - name: Link check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install the doc framework - working-directory: docs - run: | - make install - - - name: Run linkchecker - working-directory: docs - run: | - make linkcheck + documentation-checks: + uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yml@main + with: + working-directory: 'docs' \ No newline at end of file diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml index 5125782a..5f50e0f2 100644 --- a/docs/.github/workflows/automatic-doc-checks.yml +++ b/docs/.github/workflows/automatic-doc-checks.yml @@ -1,65 +1,11 @@ -name: Automatic documentation checks +name: Main Documentation Checks on: - push - pull_request -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: - spellcheck: - name: Spelling check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install Aspell - run: | - sudo apt-get install aspell aspell-en - - - name: Install the doc framework - working-directory: . - run: | - make install - - - name: Build docs and run spelling checker - working-directory: . - run: | - make spelling - - woke: - name: Inclusive language check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install the doc framework - working-directory: . - run: | - make install - - - name: Run incluse-language checker - working-directory: . - run: | - make woke - - linkcheck: - name: Link check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install the doc framework - working-directory: . - run: | - make install - - - name: Run linkchecker - working-directory: . - run: | - make linkcheck + documentation-checks: + uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yml@main + with: + working-directory: '.' \ No newline at end of file From a7000bb4e15b8a028421c943bc1fe3f9058d9fa0 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 16 Aug 2023 11:49:29 +0100 Subject: [PATCH 04/64] Dispatch event Allows it to be manually called --- .github/workflows/automatic-doc-checks.yml | 1 + docs/.github/workflows/automatic-doc-checks.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 6264eb8d..e5a83629 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -3,6 +3,7 @@ name: Main Documentation Checks on: - push - pull_request + - workflow_dispatch jobs: documentation-checks: diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml index 5f50e0f2..4420012f 100644 --- a/docs/.github/workflows/automatic-doc-checks.yml +++ b/docs/.github/workflows/automatic-doc-checks.yml @@ -3,6 +3,7 @@ name: Main Documentation Checks on: - push - pull_request + - workflow_dispatch jobs: documentation-checks: From c4eb60242af66912864cb33a5ff95fcb0d9a388a Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 16 Aug 2023 11:50:12 +0100 Subject: [PATCH 05/64] typo --- .github/workflows/automatic-doc-checks.yml | 2 +- docs/.github/workflows/automatic-doc-checks.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index e5a83629..29b297e7 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -7,6 +7,6 @@ on: jobs: documentation-checks: - uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yml@main + uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main with: working-directory: 'docs' \ No newline at end of file diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml index 4420012f..30d811bb 100644 --- a/docs/.github/workflows/automatic-doc-checks.yml +++ b/docs/.github/workflows/automatic-doc-checks.yml @@ -7,6 +7,6 @@ on: jobs: documentation-checks: - uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yml@main + uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main with: working-directory: '.' \ No newline at end of file From f47a3d240455c7d80fa04be41cf35a930e6f9a87 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 16 Aug 2023 12:53:48 +0100 Subject: [PATCH 06/64] MFlow word Added exception for spellcheck --- docs/.wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 212ce5b5..e1f95bd9 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -15,3 +15,4 @@ subtree txt UI yaml +MLflow From 1d913a90d0e6f75b089c71cff12012b11a1a8412 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 22 Aug 2023 10:22:46 +0100 Subject: [PATCH 07/64] Custom conf --- docs/.wordlist.txt | 1 + docs/custom_conf.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index e1f95bd9..763dd980 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -16,3 +16,4 @@ txt UI yaml MLflow +mlflow diff --git a/docs/custom_conf.py b/docs/custom_conf.py index 1bf7e284..f98fe299 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -28,7 +28,7 @@ # The URL of the documentation output ogp_site_url = 'https://canonical-starter-pack.readthedocs-hosted.com/' # The documentation website name (usually the same as the product name) -ogp_site_name = project +ogp_site_name = mlflow # An image or logo that is used in the preview ogp_image = 'https://assets.ubuntu.com/v1/253da317-image-document-ubuntudocs.svg' @@ -49,17 +49,17 @@ # Change to the discourse instance you want to be able to link to # using the :discourse: metadata at the top of a file # (use an empty value if you don't want to link) - 'discourse': 'https://discourse.ubuntu.com', + 'discourse': 'https://discourse.charmhub.io/', # Change to the GitHub info for your project - 'github_url': 'https://github.com/canonical/starter-pack', + 'github_url': 'https://github.com/canonical/mlflow-operator', # Change to the branch for this version of the documentation 'github_version': 'main', # Change to the folder that contains the documentation # (usually "/" or "/docs/") - 'github_folder': '/', + 'github_folder': '/docs', # Change to an empty value if your GitHub repo doesn't have issues enabled. # This will disable the feedback button and the issue link in the footer. From 8e4d7ea4e42838da08c9d752b4433f78e96e8d53 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 22 Aug 2023 10:36:04 +0100 Subject: [PATCH 08/64] quotes Values seem to be in quotes --- docs/custom_conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/custom_conf.py b/docs/custom_conf.py index f98fe299..edb470ef 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -28,7 +28,7 @@ # The URL of the documentation output ogp_site_url = 'https://canonical-starter-pack.readthedocs-hosted.com/' # The documentation website name (usually the same as the product name) -ogp_site_name = mlflow +ogp_site_name = 'mlflow' # An image or logo that is used in the preview ogp_image = 'https://assets.ubuntu.com/v1/253da317-image-document-ubuntudocs.svg' From 68aa0e4fa13562d9e098479cdcadb86662bfef7e Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 22 Aug 2023 10:42:40 +0100 Subject: [PATCH 09/64] Starter pack update Pulled in latest starter pack changes --- docs/.github/.jira_sync_config.yaml | 37 ++++++++++++ .../workflows/automatic-doc-checks.yml | 4 ++ docs/.sphinx/pinned-requirements.txt | 57 ------------------- docs/.sphinx/requirements.txt | 2 +- docs/conf.py | 5 ++ docs/readme.rst | 21 ++++++- 6 files changed, 65 insertions(+), 61 deletions(-) create mode 100644 docs/.github/.jira_sync_config.yaml delete mode 100644 docs/.sphinx/pinned-requirements.txt diff --git a/docs/.github/.jira_sync_config.yaml b/docs/.github/.jira_sync_config.yaml new file mode 100644 index 00000000..2be7bbd7 --- /dev/null +++ b/docs/.github/.jira_sync_config.yaml @@ -0,0 +1,37 @@ +settings: + # Jira project key to create the issue in + jira_project_key: "DOCPR" + + # Dictionary mapping GitHub issue status to Jira issue status + status_mapping: + opened: Untriaged + closed: Done + + # (Optional) Jira project components that should be attached to the created issue + # Component names are case-sensitive + # components: + # - IoT + # - DACH TT + + # (Optional) GitHub labels. Only issues with one of those labels will be synchronized. + # If not specified, all issues will be synchronized + #labels: + # - bug + # - custom + + # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue + add_gh_comment: true + + # (Optional) (Default: true) Synchronize issue description from GitHub to Jira + sync_description: true + + # (Optional) (Default: true) Synchronize comments from GitHub to Jira + sync_comments: true + + # (Optional) (Default: None) Parent Epic key to link the issue to + epic_key: "DOCPR-6" + + # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. + # If label on the issue is not in specified list, this issue will be created as a Bug + label_mapping: + enhancement: Story \ No newline at end of file diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml index 30d811bb..a6eb0ab7 100644 --- a/docs/.github/workflows/automatic-doc-checks.yml +++ b/docs/.github/workflows/automatic-doc-checks.yml @@ -5,6 +5,10 @@ on: - pull_request - workflow_dispatch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: documentation-checks: uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt deleted file mode 100644 index 66fc4e97..00000000 --- a/docs/.sphinx/pinned-requirements.txt +++ /dev/null @@ -1,57 +0,0 @@ -alabaster==0.7.13 -Babel==2.12.1 -beautifulsoup4==4.11.2 -bracex==2.3.post1 -certifi==2022.12.7 -charset-normalizer==3.1.0 -colorama==0.4.6 -contourpy==1.0.7 -cycler==0.11.0 -docutils==0.19 -fonttools==4.39.0 -furo==2022.12.7 -html5lib==1.1 -idna==3.4 -imagesize==1.4.1 -importlib-metadata==6.0.0 -importlib-resources==5.12.0 -Jinja2==3.1.2 -kiwisolver==1.4.4 -livereload==2.6.3 -lxd-sphinx-extensions==0.0.6 -lxml==4.9.2 -Markdown==3.4.1 -MarkupSafe==2.1.2 -matplotlib==3.7.1 -numpy==1.24.2 -packaging==23.0 -Pillow==9.4.0 -Pygments==2.14.0 -pyparsing==3.0.9 -pyspelling==2.8.2 -python-dateutil==2.8.2 -pytz==2022.7.1 -PyYAML==6.0 -requests==2.28.2 -six==1.16.0 -snowballstemmer==2.2.0 -soupsieve==2.4 -sphinx==6.1.3 -sphinx-autobuild==2021.3.14 -sphinx-basic-ng==1.0.0b1 -sphinx-copybutton==0.5.1 -sphinx-design==0.3.0 -sphinx-reredirects==0.1.1 -sphinx-tabs==3.4.1 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 -sphinxext-opengraph==0.8.1 -tornado==6.2 -urllib3==1.26.14 -wcmatch==8.4.1 -webencodings==0.5.1 -zipp==3.15.0 diff --git a/docs/.sphinx/requirements.txt b/docs/.sphinx/requirements.txt index d6f37e04..0c267c75 100644 --- a/docs/.sphinx/requirements.txt +++ b/docs/.sphinx/requirements.txt @@ -1,4 +1,4 @@ -sphinx +sphinx==7.1.2 sphinx-autobuild sphinx-design furo diff --git a/docs/conf.py b/docs/conf.py index fa20cf16..be11d82f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -85,6 +85,11 @@ if not 'conf_py_path' in html_context and 'github_folder' in html_context: html_context['conf_py_path'] = html_context['github_folder'] +# For ignoring specific links +linkcheck_anchors_ignore_for_url = [ + r'https://github\.com/.*' +] + ############################################################ ### Styling ############################################################ diff --git a/docs/readme.rst b/docs/readme.rst index 228535f0..53a017a4 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -48,8 +48,7 @@ To add documentation to an existing code repository: repository (with the exception of the ``.git`` directory) #. copy the file(s) located in the ``docs/.github/workflows`` directory into the code repository's ``.github/workflows`` directory -#. in the above workflow file(s), set the values of the ``working-directory`` - and ``workdir`` fields to ``docs`` +#. in the above workflow file(s), change the value of the ``working-directory`` field from ``.`` to ``docs`` #. in file ``docs/.readthedocs.yaml`` set the following: * ``configuration: docs/conf.py`` @@ -230,7 +229,23 @@ Add custom configuration To add custom configurations for your project, see the ``Additions to default configuration`` and ``Additional configuration`` sections in the ``custom_conf.py`` file. These can be used to extend or override the common configuration, or to define additional configuration that is not covered by the common ``conf.py`` file. +(Optional) Synchronise GitHub issues to Jira +-------------------------------------------- + +If you wish to sync issues from your documentation repository on GitHub to your +Jira board, configure the `GitHub/Jira sync bot `_ +by editing the ``.github/workflows/.jira_sync_config.yaml`` file appropriately. +In addition to updating this file, you must also apply server configuration +for this feature to work. For more information, see `server configuration details `_ +for the GitHub/Jira sync bot. + +The ``.jira_sync_config.yaml`` file that is included in the starter pack +contains configuration for syncing issues from the starter pack repository to +its documentation Jira board. +Therefore, it does not work out of the box for other repositories in GitHub, +and you must update it if you want to use the synchronisation feature. + Change log ---------- -See the `change log `_ for a list of relevant changes to the starter pack. +See the `change log `_ for a list of relevant changes to the starter pack. \ No newline at end of file From 0c1007c8d4f90d03f97d4918f16454737cd42caa Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 22 Aug 2023 10:46:06 +0100 Subject: [PATCH 10/64] Latest workflow --- .github/workflows/automatic-doc-checks.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 29b297e7..3a4fe5e2 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -5,6 +5,10 @@ on: - pull_request - workflow_dispatch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: documentation-checks: uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main From 0b20c6cca9711c292ad8370fd01e0accc2497e8c Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 22 Aug 2023 10:46:35 +0100 Subject: [PATCH 11/64] Proj name Removed "Documentation" - the site alredy appends "documentation" --- docs/custom_conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/custom_conf.py b/docs/custom_conf.py index edb470ef..81afe22e 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -15,7 +15,7 @@ ############################################################ # Product name -project = 'MLflow Documentation' +project = 'MLflow' author = 'Canonical Group Ltd' # Uncomment if your product uses release numbers From e20bde4b7d12937c2427b560218e161625035d22 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 09:12:50 +0100 Subject: [PATCH 12/64] Skeletal landing page Content as yet unchecked Also updated some links --- docs/custom_conf.py | 4 +-- docs/explanation/index.rst | 4 +++ docs/how-to/index.rst | 4 +++ docs/index.rst | 64 ++++++++++++++++++++++++++++++++------ docs/reference/index.rst | 4 +++ docs/tutorial/index.rst | 4 +++ 6 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 docs/explanation/index.rst create mode 100644 docs/how-to/index.rst create mode 100644 docs/reference/index.rst create mode 100644 docs/tutorial/index.rst diff --git a/docs/custom_conf.py b/docs/custom_conf.py index 81afe22e..1daf1d8e 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -49,7 +49,7 @@ # Change to the discourse instance you want to be able to link to # using the :discourse: metadata at the top of a file # (use an empty value if you don't want to link) - 'discourse': 'https://discourse.charmhub.io/', + 'discourse': 'https://discourse.charmhub.io/tag/mlflow', # Change to the GitHub info for your project 'github_url': 'https://github.com/canonical/mlflow-operator', @@ -59,7 +59,7 @@ # Change to the folder that contains the documentation # (usually "/" or "/docs/") - 'github_folder': '/docs', + 'github_folder': '/docs/', # Change to an empty value if your GitHub repo doesn't have issues enabled. # This will disable the feedback button and the issue link in the footer. diff --git a/docs/explanation/index.rst b/docs/explanation/index.rst new file mode 100644 index 00000000..9737e91b --- /dev/null +++ b/docs/explanation/index.rst @@ -0,0 +1,4 @@ +Explanation +=========== + +Coming soon. \ No newline at end of file diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst new file mode 100644 index 00000000..8c4809ee --- /dev/null +++ b/docs/how-to/index.rst @@ -0,0 +1,4 @@ +How-to Guides +============= + +Coming soon. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 317591c5..6d1c10c0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,20 +1,64 @@ -Starter pack -============ +:relatedlinks: [Diátaxis](https://diataxis.fr/) -This starter pack contains the files you need to quickly set up your Sphinx documentation. +.. _home: -Before you start, see the information about `Read the Docs at Canonical`_ and the instructions for `How to publish documentation on Read the Docs`_. +Mlflow Documentation +==================== -Then, to start setting up your docs, check the :doc:`ReadMe ` for instructions. -The `Example product documentation`_ shows how to set up a Diátaxis structure in Sphinx. +**Mlflow: Streamlining Machine Learning Lifecycle.** +Mlflow offers a comprehensive platform to manage the end-to-end machine learning lifecycle. -For quick help on reST or MyST syntax, see the :file:`doc-cheat-sheet.rst.txt` or :file:`doc-cheat-sheet-myst.md.txt` files in the repository. -(Open the files in your text editor; the rendered output is not very useful.) +**Mlflow provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models.** It integrates with popular machine learning frameworks and simplifies the process of handling various stages of ML projects. With Mlflow, teams can collaborate efficiently and ensure consistent and reproducible results. -More information is available in the `reStructuredText style guide`_. +**Addressing the challenges of ML projects.** Mlflow recognizes the complexities involved in machine learning projects – from data preprocessing, model training, to deployment. It provides a unified platform to streamline these processes, ensuring that projects are manageable, scalable, and reproducible. + +**Ideal for data scientists, ML engineers, and teams looking to optimize their ML workflows.** Whether you're an individual researcher or part of a large team, Mlflow offers tools and features that cater to your needs, ensuring that your ML projects are efficient and effective. + +--------- + +In this documentation +--------------------- + +.. grid:: 1 1 2 2 + + .. grid-item:: :doc:`Tutorial ` + + **Start here**: a hands-on introduction to Mlflow for newcomers + + .. grid-item:: :doc:`How-to guides ` + + **Step-by-step guides** covering key operations and common tasks in Mlflow + +.. grid:: 1 1 2 2 + :reverse: + + .. grid-item:: :doc:`Reference ` + + **Technical information** - specifications, APIs, architecture of Mlflow + + .. grid-item:: :doc:`Explanation ` + + **Discussion and clarification** of key Mlflow concepts and features + +--------- + +Project and community +--------------------- + +Mlflow is an open-source project that values its community. We warmly welcome contributions, suggestions, fixes, and constructive feedback from everyone. + +* `Code of conduct`_ +* `Get support`_ +* `Join our online chat`_ +* `Contribute`_ +* `Roadmap`_ +* `Considering Mlflow for your next ML project? Get in touch!`_ .. toctree:: :hidden: :maxdepth: 2 - ReadMe + tutorial/index + how-to/index + reference/index + explanation/index \ No newline at end of file diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 00000000..27ebe866 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,4 @@ +Reference +========= + +Coming soon. \ No newline at end of file diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst new file mode 100644 index 00000000..cf197317 --- /dev/null +++ b/docs/tutorial/index.rst @@ -0,0 +1,4 @@ +Tutorial +======== + +Coming soon. \ No newline at end of file From 0e70829c1b58a42868580f227ad88b6af7e10cea Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 10:25:34 +0100 Subject: [PATCH 13/64] Charm specific Made the content charm specific --- docs/custom_conf.py | 2 +- docs/index.rst | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/custom_conf.py b/docs/custom_conf.py index 1daf1d8e..20bb6e1d 100644 --- a/docs/custom_conf.py +++ b/docs/custom_conf.py @@ -15,7 +15,7 @@ ############################################################ # Product name -project = 'MLflow' +project = 'Charmed MLflow' author = 'Canonical Group Ltd' # Uncomment if your product uses release numbers diff --git a/docs/index.rst b/docs/index.rst index 6d1c10c0..32826efe 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,17 +2,17 @@ .. _home: -Mlflow Documentation -==================== +Charmed MLflow Documentation +============================ -**Mlflow: Streamlining Machine Learning Lifecycle.** -Mlflow offers a comprehensive platform to manage the end-to-end machine learning lifecycle. +**Charmed MLflow: Enhancing the MLflow Experience with Charms.** +Charmed MLflow offers an enhanced platform to manage the end-to-end machine learning lifecycle, leveraging the power of charms. -**Mlflow provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models.** It integrates with popular machine learning frameworks and simplifies the process of handling various stages of ML projects. With Mlflow, teams can collaborate efficiently and ensure consistent and reproducible results. +**Charmed MLflow provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models with charm integrations.** It not only integrates with popular machine learning frameworks but also brings in the robustness of charms to simplify the process of handling various stages of ML projects. With Charmed MLflow, teams can collaborate efficiently and ensure consistent, reproducible results. -**Addressing the challenges of ML projects.** Mlflow recognizes the complexities involved in machine learning projects – from data preprocessing, model training, to deployment. It provides a unified platform to streamline these processes, ensuring that projects are manageable, scalable, and reproducible. +**Addressing the challenges of ML projects with charm.** Charmed MLflow recognizes the complexities involved in machine learning projects – from data preprocessing, model training, to deployment with charms. It provides a unified platform to streamline these processes, ensuring that projects are manageable, scalable and reproducible. -**Ideal for data scientists, ML engineers, and teams looking to optimize their ML workflows.** Whether you're an individual researcher or part of a large team, Mlflow offers tools and features that cater to your needs, ensuring that your ML projects are efficient and effective. +**Ideal for data scientists, ML engineers, and teams looking to optimize their ML workflows with charms.** Whether you're an individual researcher or part of a large team, Charmed MLflow offers tools and features that cater to your needs. --------- @@ -23,36 +23,36 @@ In this documentation .. grid-item:: :doc:`Tutorial ` - **Start here**: a hands-on introduction to Mlflow for newcomers + **Start here**: a hands-on introduction to Charmed MLflow for newcomers .. grid-item:: :doc:`How-to guides ` - **Step-by-step guides** covering key operations and common tasks in Mlflow + **Step-by-step guides** covering key operations and common tasks in Charmed MLflow .. grid:: 1 1 2 2 :reverse: .. grid-item:: :doc:`Reference ` - **Technical information** - specifications, APIs, architecture of Mlflow + **Technical information** - specifications, APIs, architecture of Charmed MLflow .. grid-item:: :doc:`Explanation ` - **Discussion and clarification** of key Mlflow concepts and features + **Discussion and clarification** of key Charmed MLflow concepts and features --------- Project and community --------------------- -Mlflow is an open-source project that values its community. We warmly welcome contributions, suggestions, fixes, and constructive feedback from everyone. +Charmed MLflow is an open-source project that values its community. We warmly welcome contributions, suggestions, fixes, and constructive feedback from everyone. * `Code of conduct`_ * `Get support`_ * `Join our online chat`_ * `Contribute`_ * `Roadmap`_ -* `Considering Mlflow for your next ML project? Get in touch!`_ +* `Considering Charmed MLflow for your next ML project? Get in touch!`_ .. toctree:: :hidden: From 6608ea3813461c46ef37ac83ac6acc12fab7e5bd Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 10:39:25 +0100 Subject: [PATCH 14/64] Remove readme Removed starter pack readme --- docs/readme.rst | 251 ------------------------------------------------ 1 file changed, 251 deletions(-) delete mode 100644 docs/readme.rst diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 53a017a4..00000000 --- a/docs/readme.rst +++ /dev/null @@ -1,251 +0,0 @@ -Documentation starter pack -========================== - -See the `Sphinx and Read the Docs `_ guide for instructions on how to get started with Sphinx documentation. - -Then go through the following sections to use this starter pack to set up your docs repository. - -Set up your documentation repository ------------------------------------- - -You can either create a standalone documentation project based on this repository or include the files from this repository in a dedicated documentation folder in an existing code repository. - -**Note:** We're planning to provide the contents of this repository as an installable package in the future, but currently, you need to copy and update the required files manually. - -Standalone documentation repository -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To create a standalone documentation repository, clone this starter pack -repository, `update the configuration <#configure-the-documentation>`_, and -then commit all files to the documentation repository. - -You don't need to move any files, and you don't need to do any special -configuration on Read the Docs. - -Here is one way to do this for newly-created fictional docs repository -``canonical/alpha-docs``: - -.. code-block:: none - - git clone git@github.com:canonical/sphinx-docs-starter-pack alpha-docs - cd alpha-docs - rm -rf .git - git init - git branch -m main - UPDATE THE CONFIGURATION AND BUILD THE DOCS - git add -A - git commit -m "Import sphinx-docs-starter-pack" - git remote add upstream git@github.com:canonical/alpha-docs - git push -f upstream main - -Documentation in a code repository -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To add documentation to an existing code repository: - -#. create a directory called ``docs`` at the root of the code repository -#. populate the above directory with the contents of the starter pack - repository (with the exception of the ``.git`` directory) -#. copy the file(s) located in the ``docs/.github/workflows`` directory into - the code repository's ``.github/workflows`` directory -#. in the above workflow file(s), change the value of the ``working-directory`` field from ``.`` to ``docs`` -#. in file ``docs/.readthedocs.yaml`` set the following: - - * ``configuration: docs/conf.py`` - * ``requirements: docs/.sphinx/requirements.txt`` - -**Note:** When configuring RTD itself for your project, the setting **Path for -.readthedocs.yaml** (under **Advanced Settings**) will need to be given the -value of "docs/.readthedocs.yaml". - -Getting started ---------------- - -There are make targets defined in the ``Makefile`` that do various things. To -get started, we will: - -* install prerequisite software -* view the documentation - -Install prerequisite software -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To install the prerequisites: - -.. code-block:: none - - make install - -This will create a virtual environment (``.sphinx/venv``) and install -dependency software (``.sphinx/requirements.txt``) within it. - -A complete set of pinned, known-working dependencies is included in -``.sphinx/pinned-requirements.txt``. - -View the documentation -~~~~~~~~~~~~~~~~~~~~~~ - -To view the documentation: - -.. code-block:: none - - make run - -This will do several things: - -* activate the virtual environment -* build the documentation -* serve the documentation on **127.0.0.1:8000** -* rebuild the documentation each time a file is saved -* send a reload page signal to the browser when the documentation is rebuilt - -The ``run`` target is therefore very convenient when preparing to submit a -change to the documentation. - -Submit your change -~~~~~~~~~~~~~~~~~~ - -Prior to submitting your change, it is recommended to do a fresh build in order -to surface any errors that may cause build issues on the RTD side: - -.. code-block:: none - - make clean-doc - make html - -Configure the documentation ---------------------------- - -You must modify some of the default configuration to suit your project. -To simplify keeping your documentation in sync with the starter pack, all custom configuration is located in the ``custom_conf.py`` file. -You should never modify the common ``conf.py`` file. - -Go through all settings in the ``Project information`` section of the ``custom_conf.py`` file and update them for your project. - -See the following sections for further customisation. - -Configure the header -~~~~~~~~~~~~~~~~~~~~ - -By default, the header contains your product tag, product name (taken from the ``project`` setting in the ``custom_conf.py`` file), a link to your product page, and a drop-down menu for "More resources" that contains links to Discourse and GitHub. - -You can change any of those links or add further links to the "More resources" drop-down by editing the ``.sphinx/_templates/header.html`` file. -For example, you might want to add links to announcements, tutorials, getting started guides, or videos that are not part of the documentation. - -Configure the spelling check -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If your documentation uses US English instead of UK English, change this in the -``.sphinx/spellingcheck.yaml`` file. - -To add exceptions for words the spelling check marks as wrong even though they are correct, edit the ``.wordlist.txt`` file. - -Configure the inclusive-language check -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you can't avoid non-inclusive language in some cases, you'll need to -configure exemptions for them. - -In-file exemptions -^^^^^^^^^^^^^^^^^^ - -Suppose a reST file has a link to some site you don't control, and the address -contains "\m\a\s\t\e\r" --- a non-inclusive word. You can't change the link, -but the remainder of the file must be checked for inclusive language. Here the -``woke`` tool's `next-line ignore -`_ feature is -useful, as follows. - -If the link is in-line, move the definition to a line of its own (e.g. among -``.. LINKS`` at the bottom of the file). Above the definition, invoke the -``wokeignore`` rule for the offending word: - -.. code-block:: ReST - - .. LINKS - .. wokeignore:rule=master - .. _link anchor: https://some-external-site.io/master/some-page.html - -Exempt an entire file -^^^^^^^^^^^^^^^^^^^^^ - -If it's necessary *and safe*, you can exempt a whole file from -inclusive-language checks. To exempt ``docs/foo/bar.rst`` for example, add the -following line to ``.wokeignore``: - -.. code-block:: none - - foo/bar.rst - -.. note:: - - For ``.wokeignore`` to take effect, you must also move it into your - project's root directory. If you leave it in ``docs/``, the ``woke`` tool - won't find it and no files will be exempt. - -Change checked file-types and locations -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -By default, only reST files are checked for inclusive language --- and only -those in ``docs/`` and its subdirectories. To check Markdown files for example, -or files outside the ``docs/`` subtree, you must change how the ``woke`` tool -is invoked. - -The ``woke`` command appears twice: in the ``docs/Makefile`` and in your -project's ``.github/workflows/automatic-doc-checks.yml`` file. The command -syntax is out-of-scope here --- consult the `woke User Guide -`_. - -Configure the link check -~~~~~~~~~~~~~~~~~~~~~~~~ - -If you have links in the documentation that you don't want to be checked (for -example, because they are local links or give random errors even though they -work), you can add them to the ``linkcheck_ignore`` variable in the ``custom_conf.py`` file. - -Activate/deactivate feedback button -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A feedback button is included by default, which appears at the top of each page -in the documentation. It redirects users to your GitHub issues page, and -populates an issue for them with details of the page they were on when they -clicked the button. - -If your project does not use GitHub issues, set the ``github_issues`` variable -in the ``custom_conf.py`` file to an empty value to disable both the feedback button -and the issue link in the footer. -If you want to deactivate only the feedback button, but keep the link in the -footer, set ``disable_feedback_button`` in the ``custom_conf.py`` file to ``True``. - -Add redirects -~~~~~~~~~~~~~ - -You can add redirects to make sure existing links and bookmarks continue working when you move files around. -To do so, specify the old and new paths in the ``redirects`` setting of the ``custom_conf.py`` file. - -Add custom configuration -~~~~~~~~~~~~~~~~~~~~~~~~ - -To add custom configurations for your project, see the ``Additions to default configuration`` and ``Additional configuration`` sections in the ``custom_conf.py`` file. -These can be used to extend or override the common configuration, or to define additional configuration that is not covered by the common ``conf.py`` file. - -(Optional) Synchronise GitHub issues to Jira --------------------------------------------- - -If you wish to sync issues from your documentation repository on GitHub to your -Jira board, configure the `GitHub/Jira sync bot `_ -by editing the ``.github/workflows/.jira_sync_config.yaml`` file appropriately. -In addition to updating this file, you must also apply server configuration -for this feature to work. For more information, see `server configuration details `_ -for the GitHub/Jira sync bot. - -The ``.jira_sync_config.yaml`` file that is included in the starter pack -contains configuration for syncing issues from the starter pack repository to -its documentation Jira board. -Therefore, it does not work out of the box for other repositories in GitHub, -and you must update it if you want to use the synchronisation feature. - -Change log ----------- - -See the `change log `_ for a list of relevant changes to the starter pack. \ No newline at end of file From de32a5d6964bbeb71953853ae24849731fbbee25 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 11:08:22 +0100 Subject: [PATCH 15/64] Fixed links --- docs/index.rst | 5 +---- docs/reuse/links.txt | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 32826efe..f34c0b01 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,11 +48,8 @@ Project and community Charmed MLflow is an open-source project that values its community. We warmly welcome contributions, suggestions, fixes, and constructive feedback from everyone. * `Code of conduct`_ -* `Get support`_ -* `Join our online chat`_ * `Contribute`_ -* `Roadmap`_ -* `Considering Charmed MLflow for your next ML project? Get in touch!`_ +* `Join our online chat`_ .. toctree:: :hidden: diff --git a/docs/reuse/links.txt b/docs/reuse/links.txt index 04cfff56..08654fd1 100644 --- a/docs/reuse/links.txt +++ b/docs/reuse/links.txt @@ -2,3 +2,7 @@ .. _Read the Docs at Canonical: https://library.canonical.com/documentation/read-the-docs .. _How to publish documentation on Read the Docs: https://library.canonical.com/documentation/publish-on-read-the-docs .. _Example product documentation: https://canonical-example-product-documentation.readthedocs-hosted.com/ + +.. _Code of conduct: https://ubuntu.com/community/ethos/code-of-conduct +.. _Join our online chat: https://chat.charmhub.io/charmhub/channels/charmed-mlops +.. _Contribute: https://github.com/canonical/mlflow-operator \ No newline at end of file From 0a61638953d6591ec0def412a749448cfb7a918f Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 13:35:46 +0100 Subject: [PATCH 16/64] Tentative content Unchecked --- docs/how-to/create-ck8s-aws.rst | 91 +++++++++++++++ docs/how-to/deploy-ck8s-aws.rst | 72 ++++++++++++ docs/how-to/index.rst | 10 +- docs/tutorial/index.rst | 8 +- docs/tutorial/mlflow-kubeflow.rst | 178 ++++++++++++++++++++++++++++++ docs/tutorial/mlflow.rst | 137 +++++++++++++++++++++++ 6 files changed, 493 insertions(+), 3 deletions(-) create mode 100644 docs/how-to/create-ck8s-aws.rst create mode 100644 docs/how-to/deploy-ck8s-aws.rst create mode 100644 docs/tutorial/mlflow-kubeflow.rst create mode 100644 docs/tutorial/mlflow.rst diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst new file mode 100644 index 00000000..60657a7d --- /dev/null +++ b/docs/how-to/create-ck8s-aws.rst @@ -0,0 +1,91 @@ +Create a Charmed Kubernetes cluster for use with an MLOps platform on AWS +========================================================================= + +Welcome to the Charmed Kubernetes cluster guide. This how-to guide will take you through the steps of creating a Charmed Kubernetes cluster with an appropriate configuration for deploying an MLOps platform such as Kubeflow or MLflow. + +**Prerequisites** + +- A local machine with Ubuntu 22.04 or later. +- An AWS account (`How to create an AWS account `_). + +**Contents:** +- Install and set up AWS CLI +- Install other tools +- Setup Juju with AWS +- Create Juju controller +- Deploy Charmed Kubernetes 1.24 + +Install and set up AWS CLI +--------------------------- + +First, `install the AWS CLI `_ on your local machine, and then set it up. You can use any of the authentication methods available for the AWS CLI. For example, you can use `IAM user credentials `_. + +Install other tools +------------------- + +Next, we install the following tools: +- `juju`: Juju is an open-source application and service modeling tool that simplifies the deployment and management of complex software systems in cloud and container environments. +- `juju-wait`: CLI tool used for waiting during juju deployments. +- `kubectl`: Kubernetes client used to communicate with a Kubernetes cluster. +- `jq`: A lightweight and versatile command-line tool for parsing and manipulating JSON data. + +Run this command: + +.. code-block:: bash + + sudo snap install juju --classic --channel=2.9/stable + for snap in juju-wait kubectl jq; \ + do sudo snap install $snap --classic; \ + done + +Setup Juju with AWS +------------------- + +Set up Juju to communicate with AWS. + +.. code-block:: bash + + juju add-credential aws + +You will be prompted for information related to your AWS account that you provided while setting up the AWS CLI (e.g., access key, secret access key). + +Create Juju controller +---------------------- + +Bootstrap a Juju controller that will be responsible for deploying cluster applications. + +.. code-block:: bash + + juju bootstrap aws kf-controller + +Deploy Charmed Kubernetes 1.24 +------------------------------ + +Clone the `Charmed Kubernetes bundle repository `_, and update CPU, disk, and memory constraints to meet Kubeflow requirements. + +.. code-block:: bash + + git clone https://github.com/charmed-kubernetes/bundle.git + sed -i '/^ *charm: kubernetes-worker/,/^ *[^:]*:/s/constraints: cores=2 mem=8G root-disk=16G/constraints: cores=8 mem=32G root-disk=200G/' ./bundle/releases/1.24/bundle.yaml + +Deploy the updated Charmed Kubernetes bundle on AWS with the storage overlay. This overlay enables you to create Kubernetes volumes backed by AWS EBS. + +.. code-block:: bash + + juju deploy ./bundle/releases/1.24/bundle.yaml \ + --overlay ./bundle/overlays/aws-storage-overlay.yaml --trust + +Wait until all components are ready. + +.. code-block:: bash + + juju-wait -m default -t 3600 + +Retrieve the Kubernetes configuration from the control plane leader unit. + +.. code-block:: bash + + mkdir ~/.kube + juju ssh kubernetes-control-plane/leader -- cat config > ~/.kube/config + +Now you can use `kubectl` to talk to your newly created Charmed Kubernetes cluster. diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst new file mode 100644 index 00000000..704d18e1 --- /dev/null +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -0,0 +1,72 @@ +Deploying Charmed MLflow v2 to Charmed Kubernetes on AWS +======================================================== + +Welcome to the guide on how to deploy Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of connecting Juju to an existing Charmed Kubernetes cluster and deploying the MLflow bundle on top of it. + +**Prerequisites:** +We assume that you have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow `this guide `_ to deploy one on AWS. + +**Contents** +- Install Juju +- Connect Juju to Charmed Kubernetes cluster +- Deploy MLflow bundle +- Connect to MLflow dashboard + +Install Juju +------------ + +Install Juju: + +.. code-block:: bash + + sudo snap install juju --classic --channel=2.9/stable + +Connect Juju to Charmed Kubernetes cluster +------------------------------------------ + +Configure Juju to communicate with the Charmed Kubernetes cluster by creating a controller: + +.. code-block:: bash + + juju add-k8s charmed-k8s-aws --controller $(juju switch | cut -d: -f1) \ + --storage=cdk-ebs + +Create a model. The model name is up to you, in our case we use ``kubeflow`` as you might want to connect MLflow with Kubeflow which requires that particular name: + +.. code-block:: bash + + juju add-model kubeflow charmed-k8s-aws + +Deploy MLflow bundle +-------------------- + +Deploy the MLflow bundle: + +.. code-block:: bash + + juju deploy mlflow --channel=2.1/edge --trust + +Wait until the deployments are active: + +.. code-block:: bash + + juju-wait -m kubeflow -t 2700 + +Connect to MLflow dashboard +--------------------------- + +By default, the MLflow UI is exposed as a NodePort Kubernetes service, accessible at each node's IP address. MLflow runs on port 31380 by default. However, AWS blocks inbound traffic to this port from outside. To overcome this, we need to add an inbound rule to the security group of nodes. + +To set the security group, list all available nodes in your Kubernetes cluster and choose any ``EXTERNAL-IP`` that you will use to access the MLflow UI: + +.. code-block:: bash + + kubectl get nodes -o wide + +In your AWS account find the EC2 instance with that particular ``EXTERNAL-IP`` and enable access to the port 31380 in the inbound rules of the security group. You can use `this `_ guide for the setup. + +.. image:: upload://dLYOMTeLFJYuGaGcRPpsBpWuBbY.png + +Open a web browser and visit ``:31380`` to access the MLflow UI. + +.. image:: upload://hbDH3Ds98fqskHCvlP0euVvFqRg.png diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 8c4809ee..cf9b323a 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -1,4 +1,10 @@ -How-to Guides +How-To Guides ============= -Coming soon. \ No newline at end of file +Welcome to the How-To section. These guides provide practical instructions for specific tasks related to deploying, managing and using MLflow. + +.. toctree:: + :maxdepth: 1 + + create-ck8s-aws + deploy-ck8s-aws diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index cf197317..bddb3750 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -1,4 +1,10 @@ Tutorial ======== -Coming soon. \ No newline at end of file +Welcome to the tutorial section! Here, you will find step-by-step guides to help you get started with deploying and managing machine learning workflows using Charmed MLflow. + +.. toctree:: + :maxdepth: 1 + + mlflow + mlflow-kubeflow \ No newline at end of file diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst new file mode 100644 index 00000000..3b7afdef --- /dev/null +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -0,0 +1,178 @@ +Getting Started with Charmed MLflow and Kubeflow +================================================ + +This document will introduce you to all you need to know to get started with version 2 of Charmed MLflow along Charmed Kubeflow version 1.7. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. + +Prerequisites +------------- + +This tutorial assumes you will be deploying Kubeflow and MLflow on a public cloud VM with the following specs: + +- Runs Ubuntu 20.04 (focal) or later. +- Has at least 4 cores, 32GB RAM and 100GB of disk space available. +- Is connected to the internet for downloading the required snaps and charms. + +We’ll also assume that you have a laptop that meets the following conditions: + +- Has an SSH tunnel open to the VM with port forwarding and a SOCKS proxy. To see how to set this up, see `How to setup SSH VM Access `_. +- Runs Ubuntu 20.04 (focal) or later. +- Has a web browser installed e.g. Chrome / Firefox / Edge. + +In the remainder of this tutorial, unless otherwise stated, it is assumed you will be running all command line operations on the VM, through the open SSH tunnel. It’s also assumed you’ll be using the web browser on your local machine to access the Kubeflow and MLflow dashboards. + +.. note:: With a few tweaks to the instructions, it is possible to do this entire tutorial on a laptop, but running it on a VM will give you a better feel for deploying Kubeflow and MLflow in production. So, we recommend using the VM, even if your laptop is powerful enough to deploy Kubeflow. + +Deploy MLflow +------------- + +Follow the steps in this tutorial to deploy MLflow on your VM: `Get Started with Charmed MLflow v2 `_. Before moving on with this tutorial, confirm that you can now access the MLflow UI on http://localhost:31380. + +Deploy Kubeflow bundle +---------------------- + +Let's deploy Charmed Kubeflow alongside MLflow. Run the following command to initiate the deployment: + +.. code-block:: bash + + juju deploy kubeflow --trust --channel=1.7/stable + +Be patient. Kubeflow is large and will take a few minutes to deploy. + +Configure Dashboard Access +-------------------------- + +Run the following commands: + +.. code-block:: bash + + juju config dex-auth public-url=http://10.64.140.43.nip.io + juju config oidc-gatekeeper public-url=http://10.64.140.43.nip.io + +This tells the authentication and authorization components of the bundle that users who access the bundle will be doing so via the URL http://10.64.140.43.nip.io. In turn, this allows those components to construct appropriate responses to incoming traffic. + +Now set the dashboard username and password: + +.. code-block:: bash + + juju config dex-auth static-username=user123@email.com + juju config dex-auth static-password=user123 + +Deploy Resource Dispatcher +-------------------------- + +Next let's deploy the resource dispatcher. The resource dispatcher is an optional component which will distribute kubernetes objects related to mlflow credentials to all user namespaces in Kubeflow. This means that all your Kubeflow users can access the MLflow model registry from their namespaces. To deploy the dispatcher run the following command: + +.. code-block:: bash + + juju deploy resource-dispatcher --channel edge --trust + +This will deploy the latest edge version of the dispatcher. See `Resource Dispatcher on GitHub `_ for more info. Now we must relate the dispatcher to mlflow: + +.. code-block:: bash + + juju relate mlflow-server:secrets resource-dispatcher:secrets + juju relate mlflow-server:pod-defaults resource-dispatcher:pod-defaults + +Now, at this point, we've deployed MLflow, Kubeflow and the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. + +So, how do you know when all the charms are ready, then? You can do this using the `juju status` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: + +.. code-block:: bash + + juju status + +Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don’t worry too much about what this all means for now. If you’re interested in learning more about this command and its output, see the `Juju Status command `_. + +The main thing we’re interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become `active`, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: + +.. code-block:: bash + + watch -c 'juju status --color | grep -E "blocked|error|maintenance|waiting|App|Unit"' + +This will periodically run a `juju status` command and filter to components which are in a state of `blocked`, `error`, `maintenance` or `waiting` i.e not `active`. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. + +Don’t be surprised if some of the components’ statuses change to `blocked` or `error` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. + +.. details:: Expand to troubleshoot: Waiting for gateway relation + + An issue you might have is the `tensorboard-controller` component might be stuck with a status of `waiting` and a message “Waiting for gateway relation”. To fix this, run: + + .. code-block:: bash + + juju run --unit istio-pilot/0 -- "export JUJU_DISPATCH_PATH=hooks/config-changed; ./dispatch" + + This is a known issue, see `tensorboard-controller GitHub issue `_ for more info. + +Be patient, it can take up to an hour for all those charms to download and initialise. In the meantime, why not try our `Juju tutorial `_? + +Integrate MLflow with Notebook +------------------------------ + +In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notbook logic to talk to MLFlow in the background. Let's get started. + +First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. `user123@email.com` and `user123`. You should now see the following screen. + +Click on start setup to setup the Kubeflow user for the first time. + +Select `Finish` to finish the process. + +Now a Kubernetes namespace was created for your user. To use MLflow for this user label the namespace with following command: + +.. code-block:: bash + + microk8s kubectl label ns user123 user.kubeflow.org/enabled="true" + +You will get the following output: `namespace/user123 labeled`. + +For more info on the label command check `Kubernetes labels `_. For more info on Kubernetes namespaces for users see the `upstream docs on Multi-user isolation `_. + +Now go back to the Dashboard. From the left panel choose notebooks. Select +New Notebook. + +At this point, we can name the notebook as we want, and choose the desired image and resource limits. For now, let's just keep things simple: + +1. For `Name` enter `test-notebook`. +2. Expand the *Custom Notebook* section and for `image` select `kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0`. + +Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to `Data Volumes -> Advanced options` and from the `Configurations` drop down choose the following options: + +1. Allow access to Kubeflow pipelines. +2. Allow access to Minio. +3. Allow access to MLflow. + +.. note:: Remember we related the resource dispatcher to MLflow earlier? This is why we're seeing the Minio and MLflow options in the dropdown! + +Great, that's all the configuration for the notebook server done. Hit the Launch button to launch the notebook server. Be patient, the notebook server will take a little while to initialise. + +When the notebook server is ready, you'll see it listed in the Notebooks table with a success stauts. At this point, select `Connect` to connect to the notebook server. + +When you connect to the notebook server, you'll be taken to the notebook environment in a new tab. Because of our earlier configurations, this environment is now connected to MLflow in the background. This means the notebooks we create here can access MLflow. Cool! + +To test this, create a new notebook and paste the following command into it, in a cell: + +.. code-block:: python + + !printenv | grep MLFLOW + +Run the cell. This will print out two environment variables `MLFLOW_S3_ENDPOINT_URL` and `MLFLOW_TRACKING_URI`, confirming MLflow is indeed connected. + +Great, we've launched a notebook server that's connected to MLflow! Now let's upload some example notebooks to this server to see MLflow in practice. + +Run Mlflow examples +------------------- + +To run mlflow examples on your newly created notebook server click on the source control icon in the left most navigation bar. + +From the menu choose the `Clone a Repository` option. + +Now insert this repository address https://github.com/canonical/kubeflow-examples.git + +This will clone a whole `kubeflow-examples` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that choose the `mlflow-v2-examples` subfolder. + +There you will find two notebooks: + +- `notebook-example.ipynb`: demonstrates how to talk to MLflow from inside a notebook. The example uses a simple classifier which is stored in MLflow registry. +- `pipeline-example.ipynb`: demonstrates how to talk to MLflow from a Kubeflow pipeline. The example creates and executes a three step Kubeflow pipeline with the last step writing a model object to the MLflow registry. + +Go ahead, try those notebooks out for yourself! You can run them cell by cell using the run button, or all at once using the double chevron `>>`. + +.. note:: If you get an error in the Notebooks related to `sklearn`, try replacing `sklearn` with `scikit-learn`. See `here `_ for more details. diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst new file mode 100644 index 00000000..5ad59124 --- /dev/null +++ b/docs/tutorial/mlflow.rst @@ -0,0 +1,137 @@ +Get Started with Charmed MLflow v2 +================================== + +Welcome to the tutorial on Charmed MLflow V2! `MLflow `_ is an open-source platform, used for managing machine learning workflows. It has four primary functions that include experiment tracking, model registry, model management and code reproducibility. + +So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up an running with just a few command line commands! + +In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `, we'll deploy MLflow to a local `microk8s `_ cloud. + +**Prerequisites:** + +We are assuming that you are running this tutorial on a local machine with the following specs: +* Runs Ubuntu 22.04 or later +* Has at least 50GB free disk space + +Install and prepare MicroK8s +---------------------------- +Let's install `MicroK8s `. MicroK8s is installed from a snap package. The published snap maintains different `channels` for different releases of Kubernetes. + +.. code-block:: bash + + sudo snap install microk8s --classic --channel=1.24/stable + +For MicroK8s to work without having to use `sudo` for every command, it creates a group called `microk8s`. To make it more convenient to run commands, you will add the current user to this group: + +.. code-block:: bash + + sudo usermod -a -G microk8s $USER + newgrp microk8s + +It is also useful to make sure the user has the proper access and ownership of any `kubectl` configuration files: + +.. code-block:: bash + + sudo chown -f -R $USER ~/.kube + +Enable the following Microk8s add-ons to configure your Kubernetes cluster with extra services needed to run Charmed Kubeflow. + +.. code-block:: bash + + microk8s enable dns hostpath-storage ingress metallb:10.64.140.43-10.64.140.49 + +Here, we added a DNS service, so the applications can find each other, storage, an ingress controller so we can access Kubeflow components and the MetalLB load balancer application. +You can see that we added some detail when enabling MetalLB, in this case the address pool to use. + +> See More : `Microk8s | How to use addons `_ + +We've now installed and configured MicroK8s. It will start running automatically, but can take 5 minutes or so before it's ready for action. Run the following command to tell MicroK8s to report its status to us when it's ready: + +.. code-block:: bash + + microk8s status --wait-ready + +Be patient - this command may not return straight away. The **`--wait-ready`** flag tells MicroK8s to wait for the Kubernetes services to initialise before returning. Once MicroK8s is ready, you will see something like the following output: + +.. code-block:: bash + + microk8s is running + +Below this there will be a bunch of other information about the cluster. + +Great, we have now installed and configured MicroK8s, and it's running and ready! + +Install Juju +------------ +`Juju `_ is an operation Lifecycle manager (OLM) for clouds, bare metal or Kubernetes. We will be using it to deploy and manage the components which make up Kubeflow. + +To install Juju from snap, run this command: + +.. code-block:: bash + + sudo snap install juju --classic --channel=2.9/stable + +Now, run the following command to deploy a Juju controller to the Kubernetes we set up with MicroK8s: + +.. code-block:: bash + + juju bootstrap microk8s + +Sit tight while the command completes! The controller may take a minute or two to deploy. + +The controller is Juju’s agent, running on Kubernetes, which can be used to deploy and control the components of Kubeflow. + +Next, we'll need to add a model for Kubeflow to the controller. Run the following command to add a model called `kubeflow`: + +.. code-block:: bash + + juju add-model kubeflow + +>The model name here can be anything. We're just using `kubeflow` because often you may want to `deploy MLflow along with Kubeflow `, and in that case, the model name must be `kubeflow`. So it's not a bad habit to have. + +The controller can work with different `models`, which map 1:1 to namespaces in Kubernetes. In this case, the model name must be `kubeflow`, due to an assumption made in the upstream Kubeflow Dashboard code. + +Great job: Juju has now been installed and configured for Kubeflow! + +Deploy MLflow bundle +-------------------- +Before deploying, run these commands: + +.. code-block:: bash + + sudo sysctl fs.inotify.max_user_instances=1280 + sudo sysctl fs.inotify.max_user_watches=655360 + +We need to run the above because under the hood, microk8s uses inotify to interact with the filesystem, and in large microk8s deployments sometimes the default inotify limits are exceeded. + +Let's now use Juju to deploy Charmed MLflow. Run the following command: + +.. code-block:: bash + + juju deploy mlflow --channel=2.1/edge --trust + +This deploys the latest edge version of MLflow with `MinIO `_ as object storage and `MySQL `_ as metadata store. + +Access MLflow +------------- +To access MLflow, visit the following URL in your web browser: + +.. code-block:: bash + + http://localhost:31380/ + +This will take you to the MLflow UI. + +> Note: by default Charmed MLflow creates a `nodeport `_ on port 31380 where you can access the MLflow UI. + +That's it! Charmed MLflow has been deployed locally with microk8s and Juju. You can now start using MLflow. + +Reference: Object storage credentials +------------------------------------- +To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with minio. To get the minio credentials run the following command: + +.. code-block:: bash + + juju run-action mlflow-server/0 get-minio-credentials --wait + +This action will output `secret-key` and `secret-access-key` From 00e85c37cb69571c079856566c6c7657bff7eb17 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 23 Aug 2023 17:28:09 +0100 Subject: [PATCH 17/64] Removed V2 Added version tables instead of V2 in heading/text --- docs/how-to/deploy-ck8s-aws.rst | 8 +++++++- docs/tutorial/mlflow-kubeflow.rst | 10 +++++++++- docs/tutorial/mlflow.rst | 10 ++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 704d18e1..3067837e 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -1,6 +1,12 @@ -Deploying Charmed MLflow v2 to Charmed Kubernetes on AWS +Deploying Charmed MLflow to Charmed Kubernetes on AWS ======================================================== ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ + Welcome to the guide on how to deploy Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of connecting Juju to an existing Charmed Kubernetes cluster and deploying the MLflow bundle on top of it. **Prerequisites:** diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 3b7afdef..1e214483 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -1,7 +1,15 @@ Getting Started with Charmed MLflow and Kubeflow ================================================ -This document will introduce you to all you need to know to get started with version 2 of Charmed MLflow along Charmed Kubeflow version 1.7. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ +| Kubeflow | 1.7 | ++------------+---------+ + +This document will introduce you to all you need to know to get started with Charmed MLflow alongside Charmed Kubeflow. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. Prerequisites ------------- diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index 5ad59124..f1c77698 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -1,7 +1,13 @@ -Get Started with Charmed MLflow v2 +Get Started with Charmed MLflow ================================== -Welcome to the tutorial on Charmed MLflow V2! `MLflow `_ is an open-source platform, used for managing machine learning workflows. It has four primary functions that include experiment tracking, model registry, model management and code reproducibility. ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ + +Welcome to the tutorial on Charmed MLflow! `MLflow `_ is an open-source platform, used for managing machine learning workflows. It has four primary functions that include experiment tracking, model registry, model management and code reproducibility. So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up an running with just a few command line commands! From 9da872cd6e67da19b9855e3a727aeed3faeb938a Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 24 Aug 2023 10:30:43 +0100 Subject: [PATCH 18/64] MLflow syntax Little tweaks --- docs/tutorial/mlflow.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index f1c77698..d9207611 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -9,32 +9,34 @@ Get Started with Charmed MLflow Welcome to the tutorial on Charmed MLflow! `MLflow `_ is an open-source platform, used for managing machine learning workflows. It has four primary functions that include experiment tracking, model registry, model management and code reproducibility. -So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up an running with just a few command line commands! +So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up and running with just a few command line commands! In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `, we'll deploy MLflow to a local `microk8s `_ cloud. -**Prerequisites:** +Prerequisites +------------- We are assuming that you are running this tutorial on a local machine with the following specs: + * Runs Ubuntu 22.04 or later * Has at least 50GB free disk space Install and prepare MicroK8s ---------------------------- -Let's install `MicroK8s `. MicroK8s is installed from a snap package. The published snap maintains different `channels` for different releases of Kubernetes. +Let's install `MicroK8s `_. MicroK8s is installed from a snap package. The published snap maintains different ``channels`` for different releases of Kubernetes. .. code-block:: bash sudo snap install microk8s --classic --channel=1.24/stable -For MicroK8s to work without having to use `sudo` for every command, it creates a group called `microk8s`. To make it more convenient to run commands, you will add the current user to this group: +For MicroK8s to work without having to use ``sudo`` for every command, it creates a group called ``microk8s``. To make it more convenient to run commands, you will add the current user to this group: .. code-block:: bash sudo usermod -a -G microk8s $USER newgrp microk8s -It is also useful to make sure the user has the proper access and ownership of any `kubectl` configuration files: +It is also useful to make sure the user has the proper access and ownership of any ``kubectl`` configuration files: .. code-block:: bash @@ -57,7 +59,7 @@ We've now installed and configured MicroK8s. It will start running automatically microk8s status --wait-ready -Be patient - this command may not return straight away. The **`--wait-ready`** flag tells MicroK8s to wait for the Kubernetes services to initialise before returning. Once MicroK8s is ready, you will see something like the following output: +Be patient - this command may not return straight away. The ``--wait-ready`` flag tells MicroK8s to wait for the Kubernetes services to initialise before returning. Once MicroK8s is ready, you will see something like the following output: .. code-block:: bash @@ -87,15 +89,15 @@ Sit tight while the command completes! The controller may take a minute or two t The controller is Juju’s agent, running on Kubernetes, which can be used to deploy and control the components of Kubeflow. -Next, we'll need to add a model for Kubeflow to the controller. Run the following command to add a model called `kubeflow`: +Next, we'll need to add a model for Kubeflow to the controller. Run the following command to add a model called ``kubeflow``: .. code-block:: bash juju add-model kubeflow ->The model name here can be anything. We're just using `kubeflow` because often you may want to `deploy MLflow along with Kubeflow `, and in that case, the model name must be `kubeflow`. So it's not a bad habit to have. +>The model name here can be anything. We're just using ``kubeflow`` because often you may want to deploy MLflow along with Kubeflow, and in that case, the model name must be ``kubeflow``. So it's not a bad habit to have. -The controller can work with different `models`, which map 1:1 to namespaces in Kubernetes. In this case, the model name must be `kubeflow`, due to an assumption made in the upstream Kubeflow Dashboard code. +The controller can work with different ``models``, which map 1:1 to namespaces in Kubernetes. In this case, the model name must be ``kubeflow``, due to an assumption made in the upstream Kubeflow Dashboard code. Great job: Juju has now been installed and configured for Kubeflow! From bc59bfa7d034956c2104270971f720ce6893caaa Mon Sep 17 00:00:00 2001 From: Michal Hucko Date: Fri, 25 Aug 2023 14:27:35 +0200 Subject: [PATCH 19/64] Kf 1022 deploy mlflow kubeflow ck8s (#191) * First version * First version --- docs/how-to/deploy-ck8s-aws.rst | 1 + .../deploy-kubeflow-mlflow-ck8s-aws.rst | 37 +++++++++++++++++++ docs/how-to/index.rst | 1 + 3 files changed, 39 insertions(+) create mode 100644 docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 3067837e..ae4bd9b3 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -1,3 +1,4 @@ +.. _deploy_mlflow_ck8s_label: Deploying Charmed MLflow to Charmed Kubernetes on AWS ======================================================== diff --git a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst new file mode 100644 index 00000000..709850ba --- /dev/null +++ b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst @@ -0,0 +1,37 @@ +Integrating Charmed MLflow with Charmed Kubeflow v2 on Charmed Kubernetes +======================================================== + +Welcome to the guide on how to integrate Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of integrating charm MLflow with the Charmed Kubeflow on the Charmed Kubernetes. + +Prerequisites +-------------- +We assume that: + +* You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow `this guide `_ to deploy one on AWS. +* You have deployed charmed Kubeflow bundle. If you don't have here is `a guide `_ on how to do it. +* You have deployed charmed MLflow bundle. If you don't have here is a guide on how to do it: :ref:`deploy_mlflow_ck8s_label` . + + +Deploy resource dispatcher +-------------------------- + +Deploy resource dispatcher: + +.. code-block:: bash + + juju deploy resource-dispatcher --trust + +Relate Resource dispatcher to MLflow +------------------------------------ + +Relate Resource dispatcher to MLflow: + +.. code-block:: bash + + juju relate mlflow-server:secrets resource-dispatcher:secrets + juju relate mlflow-server:pod-defaults resource-dispatcher:pod-defaults + +Integrate MLflow with Kubeflow notebook +--------------------------------------- + +Please reffer to this doc: :ref:`mlflow-integration-label`. diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index cf9b323a..f12bb459 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -8,3 +8,4 @@ Welcome to the How-To section. These guides provide practical instructions for s create-ck8s-aws deploy-ck8s-aws + deploy-kubeflow-mlflow-ck8s-aws From 21b08ae3172cfb09f3761c1caaebbe4ab73c28de Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 25 Aug 2023 13:58:33 +0100 Subject: [PATCH 20/64] Syntax fixes --- docs/how-to/create-ck8s-aws.rst | 10 +++--- .../deploy-kubeflow-mlflow-ck8s-aws.rst | 13 ++++--- docs/tutorial/mlflow-kubeflow.rst | 34 +++++++++---------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index 60657a7d..84c12ed8 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -24,10 +24,10 @@ Install other tools ------------------- Next, we install the following tools: -- `juju`: Juju is an open-source application and service modeling tool that simplifies the deployment and management of complex software systems in cloud and container environments. -- `juju-wait`: CLI tool used for waiting during juju deployments. -- `kubectl`: Kubernetes client used to communicate with a Kubernetes cluster. -- `jq`: A lightweight and versatile command-line tool for parsing and manipulating JSON data. +- ``juju``: Juju is an open-source application and service modeling tool that simplifies the deployment and management of complex software systems in cloud and container environments. +- ``juju-wait``: CLI tool used for waiting during juju deployments. +- ``kubectl``: Kubernetes client used to communicate with a Kubernetes cluster. +- ``jq``: A lightweight and versatile command-line tool for parsing and manipulating JSON data. Run this command: @@ -88,4 +88,4 @@ Retrieve the Kubernetes configuration from the control plane leader unit. mkdir ~/.kube juju ssh kubernetes-control-plane/leader -- cat config > ~/.kube/config -Now you can use `kubectl` to talk to your newly created Charmed Kubernetes cluster. +Now you can use ``kubectl`` to talk to your newly created Charmed Kubernetes cluster. \ No newline at end of file diff --git a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst index 709850ba..e62cfa2c 100644 --- a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst +++ b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst @@ -1,5 +1,5 @@ Integrating Charmed MLflow with Charmed Kubeflow v2 on Charmed Kubernetes -======================================================== +========================================================================= Welcome to the guide on how to integrate Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of integrating charm MLflow with the Charmed Kubeflow on the Charmed Kubernetes. @@ -8,14 +8,13 @@ Prerequisites We assume that: * You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow `this guide `_ to deploy one on AWS. -* You have deployed charmed Kubeflow bundle. If you don't have here is `a guide `_ on how to do it. -* You have deployed charmed MLflow bundle. If you don't have here is a guide on how to do it: :ref:`deploy_mlflow_ck8s_label` . - +* You have deployed the Charmed Kubeflow bundle. If you don't have it, here is `a guide `_ on how to do it. +* You have deployed the Charmed MLflow bundle. If you don't have it, here is a guide on how to do it: :ref:`deploy_mlflow_ck8s_label` . Deploy resource dispatcher -------------------------- -Deploy resource dispatcher: +Deploy the resource dispatcher: .. code-block:: bash @@ -24,7 +23,7 @@ Deploy resource dispatcher: Relate Resource dispatcher to MLflow ------------------------------------ -Relate Resource dispatcher to MLflow: +Relate the Resource dispatcher to MLflow: .. code-block:: bash @@ -34,4 +33,4 @@ Relate Resource dispatcher to MLflow: Integrate MLflow with Kubeflow notebook --------------------------------------- -Please reffer to this doc: :ref:`mlflow-integration-label`. +Please refer to this doc: :ref:`mlflow-integration-label`. \ No newline at end of file diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 1e214483..197bb497 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -68,13 +68,13 @@ Now set the dashboard username and password: Deploy Resource Dispatcher -------------------------- -Next let's deploy the resource dispatcher. The resource dispatcher is an optional component which will distribute kubernetes objects related to mlflow credentials to all user namespaces in Kubeflow. This means that all your Kubeflow users can access the MLflow model registry from their namespaces. To deploy the dispatcher run the following command: +Next, let's deploy the resource dispatcher. The resource dispatcher is an optional component which will distribute Kubernetes objects related to MLflow credentials to all user namespaces in Kubeflow. This means that all your Kubeflow users can access the MLflow model registry from their namespaces. To deploy the dispatcher, run the following command: .. code-block:: bash juju deploy resource-dispatcher --channel edge --trust -This will deploy the latest edge version of the dispatcher. See `Resource Dispatcher on GitHub `_ for more info. Now we must relate the dispatcher to mlflow: +This will deploy the latest edge version of the dispatcher. See `Resource Dispatcher on GitHub `_ for more info. Now we must relate the dispatcher to MLflow: .. code-block:: bash @@ -97,7 +97,7 @@ The main thing we’re interested in at this stage is the statuses of all the ap watch -c 'juju status --color | grep -E "blocked|error|maintenance|waiting|App|Unit"' -This will periodically run a `juju status` command and filter to components which are in a state of `blocked`, `error`, `maintenance` or `waiting` i.e not `active`. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. +This will periodically run a `juju status` command and filter to components which are in a state of `blocked`, `error`, `maintenance` or `waiting` i.e. not `active`. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. Don’t be surprised if some of the components’ statuses change to `blocked` or `error` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. @@ -116,7 +116,7 @@ Be patient, it can take up to an hour for all those charms to download and initi Integrate MLflow with Notebook ------------------------------ -In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notbook logic to talk to MLFlow in the background. Let's get started. +In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLFlow in the background. Let's get started. First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. `user123@email.com` and `user123`. You should now see the following screen. @@ -124,7 +124,7 @@ Click on start setup to setup the Kubeflow user for the first time. Select `Finish` to finish the process. -Now a Kubernetes namespace was created for your user. To use MLflow for this user label the namespace with following command: +Now a Kubernetes namespace was created for your user. To use MLflow for this user, label the namespace with the following command: .. code-block:: bash @@ -132,16 +132,16 @@ Now a Kubernetes namespace was created for your user. To use MLflow for this use You will get the following output: `namespace/user123 labeled`. -For more info on the label command check `Kubernetes labels `_. For more info on Kubernetes namespaces for users see the `upstream docs on Multi-user isolation `_. +For more info on the label command, check `Kubernetes labels `_. For more info on Kubernetes namespaces for users, see the `upstream docs on Multi-user isolation `_. -Now go back to the Dashboard. From the left panel choose notebooks. Select +New Notebook. +Now go back to the Dashboard. From the left panel, choose notebooks. Select +New Notebook. At this point, we can name the notebook as we want, and choose the desired image and resource limits. For now, let's just keep things simple: -1. For `Name` enter `test-notebook`. -2. Expand the *Custom Notebook* section and for `image` select `kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0`. +1. For `Name`, enter `test-notebook`. +2. Expand the *Custom Notebook* section and for `image`, select `kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0`. -Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to `Data Volumes -> Advanced options` and from the `Configurations` drop down choose the following options: +Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to `Data Volumes -> Advanced options` and from the `Configurations` drop-down, choose the following options: 1. Allow access to Kubeflow pipelines. 2. Allow access to Minio. @@ -151,7 +151,7 @@ Now, in order to allow our notebook server access to MLflow, we need to enable s Great, that's all the configuration for the notebook server done. Hit the Launch button to launch the notebook server. Be patient, the notebook server will take a little while to initialise. -When the notebook server is ready, you'll see it listed in the Notebooks table with a success stauts. At this point, select `Connect` to connect to the notebook server. +When the notebook server is ready, you'll see it listed in the Notebooks table with a success status. At this point, select `Connect` to connect to the notebook server. When you connect to the notebook server, you'll be taken to the notebook environment in a new tab. Because of our earlier configurations, this environment is now connected to MLflow in the background. This means the notebooks we create here can access MLflow. Cool! @@ -165,21 +165,21 @@ Run the cell. This will print out two environment variables `MLFLOW_S3_ENDPOINT_ Great, we've launched a notebook server that's connected to MLflow! Now let's upload some example notebooks to this server to see MLflow in practice. -Run Mlflow examples +Run MLflow examples ------------------- -To run mlflow examples on your newly created notebook server click on the source control icon in the left most navigation bar. +To run MLflow examples on your newly created notebook server, click on the source control icon in the leftmost navigation bar. -From the menu choose the `Clone a Repository` option. +From the menu, choose the `Clone a Repository` option. Now insert this repository address https://github.com/canonical/kubeflow-examples.git -This will clone a whole `kubeflow-examples` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that choose the `mlflow-v2-examples` subfolder. +This will clone a whole `kubeflow-examples` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that, choose the `mlflow-v2-examples` subfolder. There you will find two notebooks: -- `notebook-example.ipynb`: demonstrates how to talk to MLflow from inside a notebook. The example uses a simple classifier which is stored in MLflow registry. -- `pipeline-example.ipynb`: demonstrates how to talk to MLflow from a Kubeflow pipeline. The example creates and executes a three step Kubeflow pipeline with the last step writing a model object to the MLflow registry. +- `notebook-example.ipynb`: demonstrates how to talk to MLflow from inside a notebook. The example uses a simple classifier which is stored in the MLflow registry. +- `pipeline-example.ipynb`: demonstrates how to talk to MLflow from a Kubeflow pipeline. The example creates and executes a three-step Kubeflow pipeline with the last step writing a model object to the MLflow registry. Go ahead, try those notebooks out for yourself! You can run them cell by cell using the run button, or all at once using the double chevron `>>`. From 3ee3e474f4c3b8f60c2b2640ecfe54e42d4ad578 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 25 Aug 2023 17:18:00 +0100 Subject: [PATCH 21/64] small tweaks --- docs/tutorial/mlflow-kubeflow.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 197bb497..36cb53c0 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -1,3 +1,5 @@ +.. _mlflow-integration-label: + Getting Started with Charmed MLflow and Kubeflow ================================================ @@ -157,7 +159,7 @@ When you connect to the notebook server, you'll be taken to the notebook environ To test this, create a new notebook and paste the following command into it, in a cell: -.. code-block:: python +.. code-block:: bash !printenv | grep MLFLOW From 48d373caaa61b6dce4377e08d7a13ddd5cbeb30f Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 28 Aug 2023 12:59:40 +0100 Subject: [PATCH 22/64] Dropdown Details is not a component Dropdown comes from sphynix-design --- docs/tutorial/mlflow-kubeflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 36cb53c0..dd635ea4 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -103,7 +103,7 @@ This will periodically run a `juju status` command and filter to components whic Don’t be surprised if some of the components’ statuses change to `blocked` or `error` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. -.. details:: Expand to troubleshoot: Waiting for gateway relation +.. dropdown:: Expand to troubleshoot: Waiting for gateway relation An issue you might have is the `tensorboard-controller` component might be stuck with a status of `waiting` and a message “Waiting for gateway relation”. To fix this, run: From 1d5dcfe2c00529554dcd6cafb2ee4e0dd6331ab9 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 28 Aug 2023 17:47:03 +0100 Subject: [PATCH 23/64] Links and code Fixed links and inline code formatting --- docs/tutorial/mlflow-kubeflow.rst | 34 +++++++++++++++---------------- docs/tutorial/mlflow.rst | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index dd635ea4..ebafdd50 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -85,7 +85,7 @@ This will deploy the latest edge version of the dispatcher. See `Resource Dispat Now, at this point, we've deployed MLflow, Kubeflow and the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. -So, how do you know when all the charms are ready, then? You can do this using the `juju status` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: +So, how do you know when all the charms are ready, then? You can do this using the ``juju status`` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: .. code-block:: bash @@ -93,19 +93,19 @@ So, how do you know when all the charms are ready, then? You can do this using t Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don’t worry too much about what this all means for now. If you’re interested in learning more about this command and its output, see the `Juju Status command `_. -The main thing we’re interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become `active`, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: +The main thing we’re interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become ``active``, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: .. code-block:: bash watch -c 'juju status --color | grep -E "blocked|error|maintenance|waiting|App|Unit"' -This will periodically run a `juju status` command and filter to components which are in a state of `blocked`, `error`, `maintenance` or `waiting` i.e. not `active`. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. +This will periodically run a ``juju status`` command and filter to components which are in a state of ``blocked``, ``error``, ``maintenance`` or ``waiting`` i.e. not ``active``. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. -Don’t be surprised if some of the components’ statuses change to `blocked` or `error` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. +Don’t be surprised if some of the components’ statuses change to ``blocked`` or ``error`` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. .. dropdown:: Expand to troubleshoot: Waiting for gateway relation - An issue you might have is the `tensorboard-controller` component might be stuck with a status of `waiting` and a message “Waiting for gateway relation”. To fix this, run: + An issue you might have is the ``tensorboard-controller`` component might be stuck with a status of ``waiting`` and a message “Waiting for gateway relation”. To fix this, run: .. code-block:: bash @@ -120,11 +120,11 @@ Integrate MLflow with Notebook In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLFlow in the background. Let's get started. -First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. `user123@email.com` and `user123`. You should now see the following screen. +First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. You should now see the following screen. Click on start setup to setup the Kubeflow user for the first time. -Select `Finish` to finish the process. +Select ``Finish`` to finish the process. Now a Kubernetes namespace was created for your user. To use MLflow for this user, label the namespace with the following command: @@ -140,10 +140,10 @@ Now go back to the Dashboard. From the left panel, choose notebooks. Select +New At this point, we can name the notebook as we want, and choose the desired image and resource limits. For now, let's just keep things simple: -1. For `Name`, enter `test-notebook`. -2. Expand the *Custom Notebook* section and for `image`, select `kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0`. +1. For ``Name``, enter ``test-notebook``. +2. Expand the *Custom Notebook* section and for ``image``, select ``kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0``. -Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to `Data Volumes -> Advanced options` and from the `Configurations` drop-down, choose the following options: +Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to ``Data Volumes -> Advanced options`` and from the ``Configurations`` drop-down, choose the following options: 1. Allow access to Kubeflow pipelines. 2. Allow access to Minio. @@ -153,7 +153,7 @@ Now, in order to allow our notebook server access to MLflow, we need to enable s Great, that's all the configuration for the notebook server done. Hit the Launch button to launch the notebook server. Be patient, the notebook server will take a little while to initialise. -When the notebook server is ready, you'll see it listed in the Notebooks table with a success status. At this point, select `Connect` to connect to the notebook server. +When the notebook server is ready, you'll see it listed in the Notebooks table with a success status. At this point, select ``Connect`` to connect to the notebook server. When you connect to the notebook server, you'll be taken to the notebook environment in a new tab. Because of our earlier configurations, this environment is now connected to MLflow in the background. This means the notebooks we create here can access MLflow. Cool! @@ -163,7 +163,7 @@ To test this, create a new notebook and paste the following command into it, in !printenv | grep MLFLOW -Run the cell. This will print out two environment variables `MLFLOW_S3_ENDPOINT_URL` and `MLFLOW_TRACKING_URI`, confirming MLflow is indeed connected. +Run the cell. This will print out two environment variables ``MLFLOW_S3_ENDPOINT_URL`` and ``MLFLOW_TRACKING_URI``, confirming MLflow is indeed connected. Great, we've launched a notebook server that's connected to MLflow! Now let's upload some example notebooks to this server to see MLflow in practice. @@ -172,17 +172,17 @@ Run MLflow examples To run MLflow examples on your newly created notebook server, click on the source control icon in the leftmost navigation bar. -From the menu, choose the `Clone a Repository` option. +From the menu, choose the ``Clone a Repository`` option. Now insert this repository address https://github.com/canonical/kubeflow-examples.git -This will clone a whole `kubeflow-examples` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that, choose the `mlflow-v2-examples` subfolder. +This will clone a whole ``kubeflow-examples`` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that, choose the ``mlflow-v2-examples`` subfolder. There you will find two notebooks: -- `notebook-example.ipynb`: demonstrates how to talk to MLflow from inside a notebook. The example uses a simple classifier which is stored in the MLflow registry. -- `pipeline-example.ipynb`: demonstrates how to talk to MLflow from a Kubeflow pipeline. The example creates and executes a three-step Kubeflow pipeline with the last step writing a model object to the MLflow registry. +- ``notebook-example.ipynb``: demonstrates how to talk to MLflow from inside a notebook. The example uses a simple classifier which is stored in the MLflow registry. +- ``pipeline-example.ipynb``: demonstrates how to talk to MLflow from a Kubeflow pipeline. The example creates and executes a three-step Kubeflow pipeline with the last step writing a model object to the MLflow registry. Go ahead, try those notebooks out for yourself! You can run them cell by cell using the run button, or all at once using the double chevron `>>`. -.. note:: If you get an error in the Notebooks related to `sklearn`, try replacing `sklearn` with `scikit-learn`. See `here `_ for more details. +.. note:: If you get an error in the Notebooks related to ``sklearn``, try replacing ``sklearn`` with ``scikit-learn``. See `here `_ for more details. diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index d9207611..e1784c91 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -11,7 +11,7 @@ Welcome to the tutorial on Charmed MLflow! `MLflow `_ is an So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up and running with just a few command line commands! -In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `, we'll deploy MLflow to a local `microk8s `_ cloud. +In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `_, we'll deploy MLflow to a local `microk8s `_ cloud. Prerequisites ------------- @@ -142,4 +142,4 @@ To use mlflow you need to have credentials to the object storage. The aforementi juju run-action mlflow-server/0 get-minio-credentials --wait -This action will output `secret-key` and `secret-access-key` +This action will output ``secret-key`` and ``secret-access-key``. From f67ec3572da49a299ccb14dff464f9282bd13ebf Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 28 Aug 2023 18:20:32 +0100 Subject: [PATCH 24/64] Integrate doc Fixed links and some wording --- docs/how-to/create-ck8s-aws.rst | 1 + docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst | 8 ++++---- docs/tutorial/mlflow-kubeflow.rst | 2 -- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index 84c12ed8..444cb348 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -1,3 +1,4 @@ +.. _create_mlflow_ck8s_label: Create a Charmed Kubernetes cluster for use with an MLOps platform on AWS ========================================================================= diff --git a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst index e62cfa2c..ce5a7b8a 100644 --- a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst +++ b/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst @@ -1,15 +1,15 @@ Integrating Charmed MLflow with Charmed Kubeflow v2 on Charmed Kubernetes ========================================================================= -Welcome to the guide on how to integrate Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of integrating charm MLflow with the Charmed Kubeflow on the Charmed Kubernetes. +In this guide, we will guide you through the process of integrating Charmed MLflow with Charmed Kubeflow on `Charmed Kubernetes `_. Prerequisites -------------- We assume that: -* You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow `this guide `_ to deploy one on AWS. +* You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow the `creation guide <../create-ck8s-aws>`_ to deploy one on AWS. * You have deployed the Charmed Kubeflow bundle. If you don't have it, here is `a guide `_ on how to do it. -* You have deployed the Charmed MLflow bundle. If you don't have it, here is a guide on how to do it: :ref:`deploy_mlflow_ck8s_label` . +* You have deployed the Charmed MLflow bundle. To see how, follow our `deployment guide <../deploy-ck8s-aws>`_. Deploy resource dispatcher -------------------------- @@ -33,4 +33,4 @@ Relate the Resource dispatcher to MLflow: Integrate MLflow with Kubeflow notebook --------------------------------------- -Please refer to this doc: :ref:`mlflow-integration-label`. \ No newline at end of file +Please refer to this doc: :doc:`../tutorial/mlflow-kubeflow`. \ No newline at end of file diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index ebafdd50..ea61f7fd 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -1,5 +1,3 @@ -.. _mlflow-integration-label: - Getting Started with Charmed MLflow and Kubeflow ================================================ From 6b539bf300b94b781178134e1707397f383277f2 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 29 Aug 2023 09:44:29 +0100 Subject: [PATCH 25/64] ML Kube tutorial Fixed some broken things from Sphinx migration Made some tweaks to wording --- docs/tutorial/mlflow-kubeflow.rst | 14 +++++++------- docs/tutorial/mlflow.rst | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index ea61f7fd..42b92f2b 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -9,7 +9,7 @@ Getting Started with Charmed MLflow and Kubeflow | Kubeflow | 1.7 | +------------+---------+ -This document will introduce you to all you need to know to get started with Charmed MLflow alongside Charmed Kubeflow. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. +Welcome to this tutorial on getting started with Charmed MLflow alongside Charmed Kubeflow. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. Prerequisites ------------- @@ -22,7 +22,7 @@ This tutorial assumes you will be deploying Kubeflow and MLflow on a public clou We’ll also assume that you have a laptop that meets the following conditions: -- Has an SSH tunnel open to the VM with port forwarding and a SOCKS proxy. To see how to set this up, see `How to setup SSH VM Access `_. +- Has an SSH tunnel open to the VM with port forwarding and a SOCKS proxy. To see how to set this up, see `How to setup SSH VM Access `_. - Runs Ubuntu 20.04 (focal) or later. - Has a web browser installed e.g. Chrome / Firefox / Edge. @@ -33,7 +33,7 @@ In the remainder of this tutorial, unless otherwise stated, it is assumed you wi Deploy MLflow ------------- -Follow the steps in this tutorial to deploy MLflow on your VM: `Get Started with Charmed MLflow v2 `_. Before moving on with this tutorial, confirm that you can now access the MLflow UI on http://localhost:31380. +Follow the steps in this tutorial to deploy MLflow on your VM: :doc:`mlflow`. Before moving on with this tutorial, confirm that you can now access the MLflow UI on http://localhost:31380. Deploy Kubeflow bundle ---------------------- @@ -81,7 +81,7 @@ This will deploy the latest edge version of the dispatcher. See `Resource Dispat juju relate mlflow-server:secrets resource-dispatcher:secrets juju relate mlflow-server:pod-defaults resource-dispatcher:pod-defaults -Now, at this point, we've deployed MLflow, Kubeflow and the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. +Now, at this point, we've deployed MLflow and Kubeflow and we've related them via the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. So, how do you know when all the charms are ready, then? You can do this using the ``juju status`` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: @@ -118,13 +118,13 @@ Integrate MLflow with Notebook In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLFlow in the background. Let's get started. -First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. You should now see the following screen. +First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. Click on start setup to setup the Kubeflow user for the first time. Select ``Finish`` to finish the process. -Now a Kubernetes namespace was created for your user. To use MLflow for this user, label the namespace with the following command: +Now a Kubernetes namespace was created for your user. To use MLflow with this user, label the namespace with the following command: .. code-block:: bash @@ -147,7 +147,7 @@ Now, in order to allow our notebook server access to MLflow, we need to enable s 2. Allow access to Minio. 3. Allow access to MLflow. -.. note:: Remember we related the resource dispatcher to MLflow earlier? This is why we're seeing the Minio and MLflow options in the dropdown! +.. note:: Remember we related Kubeflow to MLflow earlier using the resource dispatcher? This is why we're seeing the Minio and MLflow options in the dropdown! Great, that's all the configuration for the notebook server done. Hit the Launch button to launch the notebook server. Be patient, the notebook server will take a little while to initialise. diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index e1784c91..30985897 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -95,7 +95,7 @@ Next, we'll need to add a model for Kubeflow to the controller. Run the followin juju add-model kubeflow ->The model name here can be anything. We're just using ``kubeflow`` because often you may want to deploy MLflow along with Kubeflow, and in that case, the model name must be ``kubeflow``. So it's not a bad habit to have. +.. note:: The model name here can be anything. We're just using ``kubeflow`` because often you may want to deploy MLflow along with Kubeflow, and in that case, the model name must be ``kubeflow``. So it's not a bad habit to have. The controller can work with different ``models``, which map 1:1 to namespaces in Kubernetes. In this case, the model name must be ``kubeflow``, due to an assumption made in the upstream Kubeflow Dashboard code. @@ -130,7 +130,7 @@ To access MLflow, visit the following URL in your web browser: This will take you to the MLflow UI. -> Note: by default Charmed MLflow creates a `nodeport `_ on port 31380 where you can access the MLflow UI. +.. note:: by default Charmed MLflow creates a `nodeport `_ on port 31380 where you can access the MLflow UI. That's it! Charmed MLflow has been deployed locally with microk8s and Juju. You can now start using MLflow. From 5877cb0fe944ce6088aada400286a3bc4a1d5abb Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Tue, 29 Aug 2023 10:17:35 +0100 Subject: [PATCH 26/64] CK8s tweaks --- docs/how-to/create-ck8s-aws.rst | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index 444cb348..ee71387c 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -2,20 +2,13 @@ Create a Charmed Kubernetes cluster for use with an MLOps platform on AWS ========================================================================= -Welcome to the Charmed Kubernetes cluster guide. This how-to guide will take you through the steps of creating a Charmed Kubernetes cluster with an appropriate configuration for deploying an MLOps platform such as Kubeflow or MLflow. +This how-to guide will show you how to create a Charmed Kubernetes (CK8s) cluster with an appropriate configuration for deploying an MLOps platforms such as Kubeflow or MLflow. **Prerequisites** - A local machine with Ubuntu 22.04 or later. - An AWS account (`How to create an AWS account `_). -**Contents:** -- Install and set up AWS CLI -- Install other tools -- Setup Juju with AWS -- Create Juju controller -- Deploy Charmed Kubernetes 1.24 - Install and set up AWS CLI --------------------------- @@ -24,13 +17,7 @@ First, `install the AWS CLI Date: Tue, 29 Aug 2023 10:32:04 +0100 Subject: [PATCH 27/64] Deploy CK8s Fixed links Made some tweaks --- docs/how-to/deploy-ck8s-aws.rst | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index ae4bd9b3..5438747d 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -1,4 +1,3 @@ -.. _deploy_mlflow_ck8s_label: Deploying Charmed MLflow to Charmed Kubernetes on AWS ======================================================== @@ -8,16 +7,10 @@ Deploying Charmed MLflow to Charmed Kubernetes on AWS | MLflow | 2 | +------------+---------+ -Welcome to the guide on how to deploy Charmed MLflow on `Charmed Kubernetes `_. In this guide, we will guide you through the process of connecting Juju to an existing Charmed Kubernetes cluster and deploying the MLflow bundle on top of it. +This guide shows how to connect Juju to an existing `Charmed Kubernetes `_ (CK8s) cluster and deploy the MLflow bundle on top of it. **Prerequisites:** -We assume that you have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow `this guide `_ to deploy one on AWS. - -**Contents** -- Install Juju -- Connect Juju to Charmed Kubernetes cluster -- Deploy MLflow bundle -- Connect to MLflow dashboard +We assume that you have access to a CK8s cluster using ``kubectl``. If you don't have a cluster set up, you can follow this guide: `Create CK8s on AWS <../create-ck8s-aws>`_. Install Juju ------------ @@ -31,14 +24,14 @@ Install Juju: Connect Juju to Charmed Kubernetes cluster ------------------------------------------ -Configure Juju to communicate with the Charmed Kubernetes cluster by creating a controller: +Configure Juju to communicate with the CK8s cluster by creating a controller: .. code-block:: bash juju add-k8s charmed-k8s-aws --controller $(juju switch | cut -d: -f1) \ --storage=cdk-ebs -Create a model. The model name is up to you, in our case we use ``kubeflow`` as you might want to connect MLflow with Kubeflow which requires that particular name: +Create a model. The model name is up to you. However, if you plan to connect MLflow with Kubeflow you must use ``kubeflow`` as the model name. .. code-block:: bash @@ -62,18 +55,14 @@ Wait until the deployments are active: Connect to MLflow dashboard --------------------------- -By default, the MLflow UI is exposed as a NodePort Kubernetes service, accessible at each node's IP address. MLflow runs on port 31380 by default. However, AWS blocks inbound traffic to this port from outside. To overcome this, we need to add an inbound rule to the security group of nodes. +By default, the MLflow UI is exposed as a NodePort Kubernetes service, accessible at each node's IP address. MLflow runs on port 31380 by default. AWS nodes are EC2 instances. To connect to an instance, it must be configured to allow traffic to this port. -To set the security group, list all available nodes in your Kubernetes cluster and choose any ``EXTERNAL-IP`` that you will use to access the MLflow UI: +You can connect to any EC2 instance in the cluster. List all available nodes in your Kubernetes cluster and choose any ``EXTERNAL-IP`` that you will use to access the MLflow UI: .. code-block:: bash kubectl get nodes -o wide -In your AWS account find the EC2 instance with that particular ``EXTERNAL-IP`` and enable access to the port 31380 in the inbound rules of the security group. You can use `this `_ guide for the setup. - -.. image:: upload://dLYOMTeLFJYuGaGcRPpsBpWuBbY.png +In your AWS account find the EC2 instance with that particular ``EXTERNAL-IP`` and enable access to the port 31380 in the inbound rules of the security group. To see how, consult `AWS docs `_. Open a web browser and visit ``:31380`` to access the MLflow UI. - -.. image:: upload://hbDH3Ds98fqskHCvlP0euVvFqRg.png From 01bdfac87330d27923cb3bfbac3e395174632e3c Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 30 Aug 2023 10:28:08 +0100 Subject: [PATCH 28/64] Deploy EKS --- docs/how-to/deploy-eks.rst | 68 ++++++++++++++++++++++++++++++++++++++ docs/how-to/index.rst | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 docs/how-to/deploy-eks.rst diff --git a/docs/how-to/deploy-eks.rst b/docs/how-to/deploy-eks.rst new file mode 100644 index 00000000..eec686da --- /dev/null +++ b/docs/how-to/deploy-eks.rst @@ -0,0 +1,68 @@ +Deploy Charmed MLflow to EKS +============================ + ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ + +This guide shows how to deploy Charmed MLflow on `AWS Elastic Kubernetes Service `_ (EKS). In this guide, we will create an AWS EKS cluster, connect Juju to it, and deploy the MLflow bundle. + +Prerequisites: +-------------- +We assume the following: + +- Your machine runs Ubuntu 22.04 or later +- You have an AWS account (`How to create an AWS account `_) + +Create EKS cluster +------------------- +See the `EKS creation guide `_ for how to do that. + +Setup Juju +---------- + +Set up your local ``juju`` to talk to the remote Kubernetes (K8s) cloud. First, install ``juju``: + +.. code-block:: bash + + sudo snap install juju --classic + +Connect Juju to Kubernetes: + +.. code-block:: bash + + juju add-k8s kubeflow + +.. note:: You must choose the name ``kubeflow`` if you plan to connect MLflow to Kubeflow. Otherwise you can choose any name. + +Create a controller: + +.. code-block:: bash + + juju bootstrap --no-gui kubeflow kubeflow-controller + +.. note:: You can use whatever controller name you like here, we chose ``kubeflow-controller``. + +Add a juju model: + +.. code-block:: bash + + juju add-model kubeflow + +.. note:: You must choose the name ``kubeflow`` if you plan to connect MLflow to Kubeflow. Otherwise you can choose any name. + +Deploy MLflow bundle +--------------------- +Deploy the MLflow bundle with the following command: + +.. code-block:: bash + + juju deploy mlflow --channel=2.1/edge --trust + +Wait until all charms are in the active state. You can check the state of the charms with the command: + +.. code-block:: bash + + juju status --watch 5s --relations diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index f12bb459..2fc0e374 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -6,6 +6,7 @@ Welcome to the How-To section. These guides provide practical instructions for s .. toctree:: :maxdepth: 1 - create-ck8s-aws + create-ck8s-aws deploy-ck8s-aws + deploy-eks deploy-kubeflow-mlflow-ck8s-aws From cb286d8c221fe2dd5a90087e7729975d8caf7cf5 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 30 Aug 2023 11:03:04 +0100 Subject: [PATCH 29/64] ML KF EKS --- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 130 +++++++++++++++++++++ docs/how-to/index.rst | 1 + 2 files changed, 131 insertions(+) create mode 100644 docs/how-to/deploy-mlflow-kubeflow-eks.rst diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst new file mode 100644 index 00000000..87d08bfe --- /dev/null +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -0,0 +1,130 @@ +Deploy Charmed MLflow and Kubeflow to EKS +========================================= + ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ + +This guide shows how to deploy Charmed MLflow alongside Kubeflow on `AWS Elastic Kubernetes Service `_ (EKS). In this guide, we will create an AWS EKS cluster, connect Juju to it, deploy the MLflow and Kubeflow bundles, and relate them to each other. + +Prerequisites +------------- + +We assume the following: + +- Your machine runs Ubuntu 22.04 or later +- You have an AWS account (`How to create an AWS account `_) + +Deploy EKS cluster +------------------- + +See our `EKS creation guide `_ for a complete guide on how to do this. **Do not forget** to edit the ``instanceType`` field under ``managedNodeGroups[0].instanceType`` from ``t2.2xlarge`` to ``t3.2xlarge``, as instructed in the guide, since worker nodes of type ``t3.2xlarge`` are required for deploying both MLFlow and Kubeflow. + +Setup Juju +---------- + +Set up your local ``juju`` to talk to the remote Kubernetes cloud. First, install juju with: + +.. code-block:: bash + + sudo snap install juju --classic + +Connect it to Kubernetes: + +.. code-block:: bash + + juju add-k8s kubeflow + +Create the controller: + +.. code-block:: bash + + juju bootstrap --no-gui kubeflow kubeflow-controller + +.. note:: we chose the name ``kubeflow-controller``, but you can choose any other name. + +Add a juju model: + +.. code-block:: bash + + juju add-model kubeflow + +Deploy MLflow bundle +--------------------- + +Deploy the MLflow bundle with the following command: + +.. code-block:: bash + + juju deploy mlflow --channel=2.1/edge --trust + +Wait until all charms are in the active state. You can check the state of the charms with the command: + +.. code-block:: bash + + juju status --watch 5s --relations + +Deploy Kubeflow bundle +---------------------- + +Deploy the Kubeflow bundle with the following command: + +.. code-block:: bash + + juju deploy kubeflow --channel=1.7/stable --trust + +Wait until all charms are in the active state. You can check the state of the charms with the command: + +.. code-block:: bash + + juju status --watch 5s --relations + +Relate MLflow to Kubeflow +------------------------- + +The resource dispatcher is used to connect MLflow with Kubeflow. In particular, it is responsible for configuring MLflow related Kubernetes objects for Kubeflow user namespaces. Deploy the resource dispatcher to the cluster with the command: + +.. code-block:: bash + + juju deploy resource-dispatcher --channel edge --trust + +Relate the resource dispatcher to MLflow with the following commands: + +.. code-block:: bash + + juju relate mlflow-server:secrets resource-dispatcher:secrets + juju relate mlflow-server:pod-defaults resource-dispatcher:pod-defaults + +Wait until all charms are in the active state. You can check the state of the charms with the command: + +.. code-block:: bash + + juju status --watch 5s --relations + +Configure Kubeflow dashboard +---------------------------- + +Get the hostname from the ``istio-ingressgateway-workload`` Kubernetes load balancer service: + +.. code-block:: bash + + export INGRESS_HOST=$(kubectl get svc -n kubeflow istio-ingressgateway-workload -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') + +Then, configure OIDC and DEX with the ``INGRESS_HOST`` we just retrieved, and also a username and password of your choosing: + +.. code-block:: bash + + juju config dex-auth public-url="http://${INGRESS_HOST}" + juju config oidc-gatekeeper public-url="http://${INGRESS_HOST}" + juju config dex-auth static-password=user123 + juju config dex-auth static-username=user123@email.com + +Wait until all charms are in the active state. You can check the state of the charms with the command: + +.. code-block:: bash + + juju status --watch 5s --relations + +Now you can access the Kubeflow dashboard at the value from ``INGRESS_HOST`` in your browser. \ No newline at end of file diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 2fc0e374..0f869dc2 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -10,3 +10,4 @@ Welcome to the How-To section. These guides provide practical instructions for s deploy-ck8s-aws deploy-eks deploy-kubeflow-mlflow-ck8s-aws + deploy-mlflow-kubeflow-eks From 8797fc2c30fa27086099807afd24ad43ddb45f92 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 30 Aug 2023 13:48:52 +0100 Subject: [PATCH 30/64] Migrate guide --- docs/how-to/index.rst | 1 + docs/how-to/migrate-v1-v2.rst | 131 ++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 docs/how-to/migrate-v1-v2.rst diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 0f869dc2..9da61df6 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -11,3 +11,4 @@ Welcome to the How-To section. These guides provide practical instructions for s deploy-eks deploy-kubeflow-mlflow-ck8s-aws deploy-mlflow-kubeflow-eks + migrate-v1-v2 diff --git a/docs/how-to/migrate-v1-v2.rst b/docs/how-to/migrate-v1-v2.rst new file mode 100644 index 00000000..8883d5e3 --- /dev/null +++ b/docs/how-to/migrate-v1-v2.rst @@ -0,0 +1,131 @@ +Migrate Charmed MLflow Version 1 to Version 2 +===================================================== + +This guide shows how to migrate Charmed MLflow version 1 to version 2. This guide assumes you are running the old Charmed MLflow stack version 1, which runs with MariaDB. With MLflow version 2, we only support the MySQL integration. This guide outlines how to move data from MariaDB to MySQL and how to migrate data from version 1 to version 2.1.1. Data from the object store doesn't need to be migrated. + +Prerequisites +------------- + +This guide assumes the following: + +#. You have deployed MLflow version 1 with MariaDB, MLflow server version 1.x, and MinIO. +#. You have CLI access to the machine where the Juju controller is deployed (all commands will be executed from there). + +MariaDB Backup +-------------- + +Install the ``mysqldump`` command: + +.. code-block:: bash + + sudo apt update + sudo apt install mysql-client + +Backup the MariaDB database with the following command: + +.. code-block:: bash + + mysqldump --host= --user=root --password=root --column-statistics=0 --databases database > mlflow-db.sql + +Deploy MySQL Charm +------------------- + +Deploy the MySQL charm, which is needed for MLflow v2: + +.. code-block:: bash + + juju deploy mysql-k8s --channel 8.0/beta --series jammy --trust + +.. note:: For MLflow version ``v.2.1``, we deploy the 8.0/beta version of the charm. You may deploy a more up to date version in your case. + +Please wait until the charm goes to active in ``juju status``. Then run the following command to get the password for MySQL: + +.. code-block:: bash + + juju run-action mysql-k8s/0 get-password --wait + +Adjust the Database Backup +-------------------------- + +Rename the database from ``database`` (used in MariaDB) to ``mlflow`` (used in MySQL): + +.. code-block:: bash + + sed 's/`database`/`mlflow`/g' mlflow-db.sql > mlflow-db-updated.sql + +Rename one duplicate constraint as MySQL does not allow that: + +.. code-block:: bash + + sed -i '0,/`CONSTRAINT_1`/s//`CONSTRAINT-1`/' mlflow-db-updated.sql + +You can do all the above modifications in the text editor of your choice if you prefer. + +Move Database to MySQL +---------------------- + +Install the MySQL CLI tool: + +.. code-block:: bash + + sudo apt update + sudo apt-get install mysql-shell + +Connect to the MySQL charm: + +.. code-block:: bash + + mysql --user=root --host= -p + # you will be prompted for password + +Create the MySQL database called ``mlflow``: + +.. code-block:: bash + + CREATE DATABASE mlflow; + +Leave the client with ``ctrl + D``. + +Move the updated database dump file to MySQL: + +.. code-block:: bash + + mysql -u root -p mlflow @/mlflow + +Update MLflow Server +--------------------- + +Remove relations from the old MLflow server: + +.. code-block:: bash + + juju remove-relation mlflow-db:mysql mlflow-server:db + juju remove-relation minio mlflow-server + +Update the MLflow server: + +.. code-block:: bash + + juju refresh mlflow-server --channel 2.1/edge + +Create relations with MinIO and MySQL: + +.. code-block:: bash + + juju relate mysql-k8s mlflow-server + juju relate minio mlflow-server From 2bfc2bcdb400296edd9c531a9ece16bb60247fc5 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Wed, 30 Aug 2023 14:17:12 +0100 Subject: [PATCH 31/64] Cos integration --- docs/how-to/cos-integration.rst | 88 +++++++++++++++++++++++++++++++++ docs/how-to/index.rst | 3 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 docs/how-to/cos-integration.rst diff --git a/docs/how-to/cos-integration.rst b/docs/how-to/cos-integration.rst new file mode 100644 index 00000000..15f7535e --- /dev/null +++ b/docs/how-to/cos-integration.rst @@ -0,0 +1,88 @@ +Integrate MLflow with the Canonical Observability Stack (COS) +============================================================= + +This guide shows how to integrate MLflow with the Canonical Observability Stack (COS). + +Prerequisites +------------- + +This guide asssumes: + +#. You have deployed the COS stack in the ``cos`` model. For steps on how to do this, see the `MicroK8s tutorial `_. +#. You have deployed the MLflow bundle in the ``kubeflow`` model. For steps on how to do this, see :doc:`../tutorial/mlflow`. + +Deploy Grafana Agent +-------------------- + +Deploy the `Grafana Agent `_ to your ``kubeflow`` model alongside the MLflow bundle. Run the following command: + +.. code-block:: bash + + juju deploy grafana-agent-k8s --channel=edge --trust + +Relate MLflow Server Prometheus Metrics to Grafana Agent +-------------------------------------------------------- + +Establish the relationship between the MLflow Server Prometheus metrics and the Grafana Agent. Use the following command: + +.. code-block:: bash + + juju add-relation mlflow-server:metrics-endpoint grafana-agent-k8s:metrics-endpoint + +Relate Grafana Agent to Prometheus in the COS Model +--------------------------------------------------- + +Next, relate the Grafana Agent to Prometheus in the ``cos`` model. Execute the following command: + +.. code-block:: bash + + juju add-relation grafana-agent-k8s admin/cos.prometheus-receive-remote-write + +Relate MLflow Server in the Kubeflow Model to Grafana Charm in the COS Model +---------------------------------------------------------------------------- + +Establish the relationship between the MLflow Server in the ``kubeflow`` model and the Grafana charm in the ``cos`` model. Run the following command: + +.. code-block:: bash + + juju add-relation mlflow-server admin/cos.grafana-dashboards + +Obtain the Grafana Dashboard Admin Password +------------------------------------------- + +Switch the model to ``cos`` and retrieve the Grafana dashboard admin password. Execute the following commands: + +.. code-block:: bash + + juju switch cos + juju run-action grafana/0 get-admin-password --wait + +Obtain the Grafana Dashboard URL +-------------------------------- + +To access the Grafana dashboard, you need the URL. Run the following command to get the URLs for the COS endpoints: + +.. code-block:: bash + + juju show-unit catalogue/0 | grep url + +You will see a list of endpoints similar to the following: + +.. code-block:: bash + + url: http://10.43.8.34:80/cos-catalogue + url: http://10.43.8.34/cos-grafana + url: http://10.43.8.34:80/cos-prometheus-0 + url: http://10.43.8.34:80/cos-alertmanager + +Choose the ``cos-grafana`` URL and access it in your browser. + +Login to Grafana +---------------- + +Login to Grafana with the password obtained from the previous section. The username is ``admin``. + +Access the dashboard in the UI +------------------------------ + +Go to the left sidebar and choose the MLflow Dashboards from the list. From the General dashboards folder choose the ``MLflow metrics Dashboard``. When accessing the dashboard for the first time, choose some reasonable time range from the top right dropdown. diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 9da61df6..7ff4a331 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -6,7 +6,8 @@ Welcome to the How-To section. These guides provide practical instructions for s .. toctree:: :maxdepth: 1 - create-ck8s-aws + cos-integration + create-ck8s-aws deploy-ck8s-aws deploy-eks deploy-kubeflow-mlflow-ck8s-aws From ca2347eed4c480dcf26f890a6d87b40eba66b491 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 31 Aug 2023 13:23:00 +0100 Subject: [PATCH 32/64] Jira to wordlist --- docs/.wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 763dd980..51c7cae0 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -17,3 +17,4 @@ UI yaml MLflow mlflow +Jira From dc147306cfc21558cd3bcf513994223cfc87ca92 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 31 Aug 2023 13:23:57 +0100 Subject: [PATCH 33/64] reorder wordlist --- docs/.wordlist.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 51c7cae0..499248c0 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -15,6 +15,6 @@ subtree txt UI yaml +Jira MLflow mlflow -Jira From dfb241abb745712e48bb299878f6248703452b56 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 31 Aug 2023 13:48:08 +0100 Subject: [PATCH 34/64] remove label --- docs/how-to/create-ck8s-aws.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index ee71387c..f1b14d6b 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -1,4 +1,3 @@ -.. _create_mlflow_ck8s_label: Create a Charmed Kubernetes cluster for use with an MLOps platform on AWS ========================================================================= From 01d272de017f2b9c0185a9128f703a90a0e520a2 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 4 Sep 2023 12:33:21 +0100 Subject: [PATCH 35/64] Project and Community Added some links --- docs/index.rst | 2 ++ docs/reuse/links.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index f34c0b01..3974abc2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,8 @@ Charmed MLflow is an open-source project that values its community. We warmly we * `Code of conduct`_ * `Contribute`_ * `Join our online chat`_ +* `Upstream Project`_ +* `Discourse Forum`_ .. toctree:: :hidden: diff --git a/docs/reuse/links.txt b/docs/reuse/links.txt index 08654fd1..bd461c26 100644 --- a/docs/reuse/links.txt +++ b/docs/reuse/links.txt @@ -5,4 +5,6 @@ .. _Code of conduct: https://ubuntu.com/community/ethos/code-of-conduct .. _Join our online chat: https://chat.charmhub.io/charmhub/channels/charmed-mlops -.. _Contribute: https://github.com/canonical/mlflow-operator \ No newline at end of file +.. _Contribute: https://github.com/canonical/mlflow-operator +.. _Upstream Project: https://mlflow.org/ +.. _Discourse Forum: https://discourse.charmhub.io/tag/mlflow \ No newline at end of file From 624741fe64f923df8a6ddbcbff310ce59336b954 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 4 Sep 2023 14:17:17 +0100 Subject: [PATCH 36/64] Alphabetical howtos --- docs/how-to/deploy-ck8s-aws.rst | 2 +- docs/how-to/index.rst | 8 ++++---- docs/how-to/{cos-integration.rst => integrate-cos.rst} | 0 ...flow-mlflow-ck8s-aws.rst => integrate-ml-ckf-ck8s.rst} | 8 +++++++- 4 files changed, 12 insertions(+), 6 deletions(-) rename docs/how-to/{cos-integration.rst => integrate-cos.rst} (100%) rename docs/how-to/{deploy-kubeflow-mlflow-ck8s-aws.rst => integrate-ml-ckf-ck8s.rst} (87%) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 5438747d..e17da187 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -1,4 +1,4 @@ -Deploying Charmed MLflow to Charmed Kubernetes on AWS +Deploy Charmed MLflow to Charmed Kubernetes on AWS ======================================================== +------------+---------+ diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 7ff4a331..a037adb3 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -6,10 +6,10 @@ Welcome to the How-To section. These guides provide practical instructions for s .. toctree:: :maxdepth: 1 - cos-integration create-ck8s-aws - deploy-ck8s-aws + deploy-ck8s-aws deploy-eks - deploy-kubeflow-mlflow-ck8s-aws - deploy-mlflow-kubeflow-eks + deploy-mlflow-kubeflow-eks + integrate-cos + integrate-ml-ckf-ck8s migrate-v1-v2 diff --git a/docs/how-to/cos-integration.rst b/docs/how-to/integrate-cos.rst similarity index 100% rename from docs/how-to/cos-integration.rst rename to docs/how-to/integrate-cos.rst diff --git a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst b/docs/how-to/integrate-ml-ckf-ck8s.rst similarity index 87% rename from docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst rename to docs/how-to/integrate-ml-ckf-ck8s.rst index ce5a7b8a..fbd6f89a 100644 --- a/docs/how-to/deploy-kubeflow-mlflow-ck8s-aws.rst +++ b/docs/how-to/integrate-ml-ckf-ck8s.rst @@ -1,6 +1,12 @@ -Integrating Charmed MLflow with Charmed Kubeflow v2 on Charmed Kubernetes +Integrate Charmed MLflow with Charmed Kubeflow on Charmed Kubernetes ========================================================================= ++------------+---------+ +| Component | Version | ++============+=========+ +| MLflow | 2 | ++------------+---------+ + In this guide, we will guide you through the process of integrating Charmed MLflow with Charmed Kubeflow on `Charmed Kubernetes `_. Prerequisites From 5932aef62712e912426f7b39e0f4401b0e031045 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 4 Sep 2023 14:25:11 +0100 Subject: [PATCH 37/64] Duplicate constraint wording Made the bit about duplicate constraints clearer --- docs/how-to/migrate-v1-v2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/migrate-v1-v2.rst b/docs/how-to/migrate-v1-v2.rst index 8883d5e3..ed0bc17a 100644 --- a/docs/how-to/migrate-v1-v2.rst +++ b/docs/how-to/migrate-v1-v2.rst @@ -53,7 +53,7 @@ Rename the database from ``database`` (used in MariaDB) to ``mlflow`` (used in M sed 's/`database`/`mlflow`/g' mlflow-db.sql > mlflow-db-updated.sql -Rename one duplicate constraint as MySQL does not allow that: +Rename any duplicate constraints as MySQL does not allow that. In practice, the only duplicate constraint we've encountered is ``CONSTRAINT_1``. It has two occurrences. The first occurrence can be renamed to ``CONSTRAINT-1``, for example: .. code-block:: bash From a01e7695d1bb67e4d893b7519c7db451207fb3bb Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 10:27:31 +0100 Subject: [PATCH 38/64] Contributing link --- docs/reuse/links.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reuse/links.txt b/docs/reuse/links.txt index bd461c26..10e483ae 100644 --- a/docs/reuse/links.txt +++ b/docs/reuse/links.txt @@ -5,6 +5,6 @@ .. _Code of conduct: https://ubuntu.com/community/ethos/code-of-conduct .. _Join our online chat: https://chat.charmhub.io/charmhub/channels/charmed-mlops -.. _Contribute: https://github.com/canonical/mlflow-operator +.. _Contribute: https://github.com/canonical/mlflow-operator/blob/main/CONTRIBUTING.md .. _Upstream Project: https://mlflow.org/ .. _Discourse Forum: https://discourse.charmhub.io/tag/mlflow \ No newline at end of file From 63a76fe920a58d9dc6e3842a85b4081b44bc0553 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 10:46:39 +0100 Subject: [PATCH 39/64] How-to toc --- docs/how-to/create-ck8s-aws.rst | 4 ++-- docs/how-to/index.rst | 32 ++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index f1b14d6b..3672931a 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -1,5 +1,5 @@ -Create a Charmed Kubernetes cluster for use with an MLOps platform on AWS -========================================================================= +Create an MLOps-ready Charmed Kubernetes cluster +================================================ This how-to guide will show you how to create a Charmed Kubernetes (CK8s) cluster with an appropriate configuration for deploying an MLOps platforms such as Kubeflow or MLflow. diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index a037adb3..1dd506bb 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -1,15 +1,39 @@ -How-To Guides +How-to guides ============= -Welcome to the How-To section. These guides provide practical instructions for specific tasks related to deploying, managing and using MLflow. +These guides provide practical instructions for specific tasks related to deploying, managing and using MLflow. + +Preparation +----------- .. toctree:: :maxdepth: 1 create-ck8s-aws + +Deployment +---------- + +.. toctree:: + :maxdepth: 1 + deploy-ck8s-aws deploy-eks - deploy-mlflow-kubeflow-eks + deploy-mlflow-kubeflow-eks + +Integration +----------- + +.. toctree:: + :maxdepth: 1 + integrate-cos integrate-ml-ckf-ck8s - migrate-v1-v2 + +Upgrading +--------- + +.. toctree:: + :maxdepth: 1 + + migrate-v1-v2 \ No newline at end of file From 81a88174a9bf902c7e9332090718e48e61b6157f Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 11:22:59 +0100 Subject: [PATCH 40/64] Tutorial --- docs/tutorial/index.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index bddb3750..cf9a0654 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -1,10 +1,18 @@ Tutorial ======== -Welcome to the tutorial section! Here, you will find step-by-step guides to help you get started with deploying and managing machine learning workflows using Charmed MLflow. +Step-by-step guides to help you get started with deploying and managing machine learning workflows using Charmed MLflow. + +We provide two pathways. For users who just want to try MLflow: .. toctree:: :maxdepth: 1 mlflow + +For users who want to try MLflow and Kubeflow together: + +.. toctree:: + :maxdepth: 1 + mlflow-kubeflow \ No newline at end of file From fdc2e8ca18c970eed2b8f54c62a719c9a3a472de Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 13:19:58 +0100 Subject: [PATCH 41/64] Streamlined intro --- docs/index.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 3974abc2..4a1782ed 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,14 +5,13 @@ Charmed MLflow Documentation ============================ -**Charmed MLflow: Enhancing the MLflow Experience with Charms.** -Charmed MLflow offers an enhanced platform to manage the end-to-end machine learning lifecycle, leveraging the power of charms. +Charmed MLflow is a platform for managing the end-to-end machine learning lifecycle. -**Charmed MLflow provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models with charm integrations.** It not only integrates with popular machine learning frameworks but also brings in the robustness of charms to simplify the process of handling various stages of ML projects. With Charmed MLflow, teams can collaborate efficiently and ensure consistent, reproducible results. +It provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models. It integrates with popular machine learning frameworks. -**Addressing the challenges of ML projects with charm.** Charmed MLflow recognizes the complexities involved in machine learning projects – from data preprocessing, model training, to deployment with charms. It provides a unified platform to streamline these processes, ensuring that projects are manageable, scalable and reproducible. +It addresses a number of challenges facing machine learning teams: collaboration, reproducibility, maintenance, organisation and scaling. -**Ideal for data scientists, ML engineers, and teams looking to optimize their ML workflows with charms.** Whether you're an individual researcher or part of a large team, Charmed MLflow offers tools and features that cater to your needs. +It's ideal for data scientists, ML engineers, hobbyists and teams looking to optimize their ML workflows with charms. --------- From e1e94cf50b114ea95292466fa654936ab62da831 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 13:44:26 +0100 Subject: [PATCH 42/64] Delete docs/.github Relic of starter pack --- docs/.github/.jira_sync_config.yaml | 37 ------------------- .../workflows/automatic-doc-checks.yml | 16 -------- 2 files changed, 53 deletions(-) delete mode 100644 docs/.github/.jira_sync_config.yaml delete mode 100644 docs/.github/workflows/automatic-doc-checks.yml diff --git a/docs/.github/.jira_sync_config.yaml b/docs/.github/.jira_sync_config.yaml deleted file mode 100644 index 2be7bbd7..00000000 --- a/docs/.github/.jira_sync_config.yaml +++ /dev/null @@ -1,37 +0,0 @@ -settings: - # Jira project key to create the issue in - jira_project_key: "DOCPR" - - # Dictionary mapping GitHub issue status to Jira issue status - status_mapping: - opened: Untriaged - closed: Done - - # (Optional) Jira project components that should be attached to the created issue - # Component names are case-sensitive - # components: - # - IoT - # - DACH TT - - # (Optional) GitHub labels. Only issues with one of those labels will be synchronized. - # If not specified, all issues will be synchronized - #labels: - # - bug - # - custom - - # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue - add_gh_comment: true - - # (Optional) (Default: true) Synchronize issue description from GitHub to Jira - sync_description: true - - # (Optional) (Default: true) Synchronize comments from GitHub to Jira - sync_comments: true - - # (Optional) (Default: None) Parent Epic key to link the issue to - epic_key: "DOCPR-6" - - # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. - # If label on the issue is not in specified list, this issue will be created as a Bug - label_mapping: - enhancement: Story \ No newline at end of file diff --git a/docs/.github/workflows/automatic-doc-checks.yml b/docs/.github/workflows/automatic-doc-checks.yml deleted file mode 100644 index a6eb0ab7..00000000 --- a/docs/.github/workflows/automatic-doc-checks.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Main Documentation Checks - -on: - - push - - pull_request - - workflow_dispatch - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - documentation-checks: - uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main - with: - working-directory: '.' \ No newline at end of file From af74ed89ac275b9c7e2ebcb7d0140a1217724160 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 16:26:09 +0100 Subject: [PATCH 43/64] Newline --- .github/workflows/automatic-doc-checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 3a4fe5e2..cba712e5 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -13,4 +13,4 @@ jobs: documentation-checks: uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main with: - working-directory: 'docs' \ No newline at end of file + working-directory: 'docs' From 1a6748e9b89013359cedcfcd968a373bbe056a2d Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 16:28:51 +0100 Subject: [PATCH 44/64] MicroK8s casing --- docs/tutorial/mlflow.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index 30985897..fc852a25 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -11,7 +11,7 @@ Welcome to the tutorial on Charmed MLflow! `MLflow `_ is an So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up and running with just a few command line commands! -In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `_, we'll deploy MLflow to a local `microk8s `_ cloud. +In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `_, we'll deploy MLflow to a local `MicroK8s `_ cloud. Prerequisites ------------- @@ -23,7 +23,7 @@ We are assuming that you are running this tutorial on a local machine with the f Install and prepare MicroK8s ---------------------------- -Let's install `MicroK8s `_. MicroK8s is installed from a snap package. The published snap maintains different ``channels`` for different releases of Kubernetes. +Let's install MicroK8s. MicroK8s is installed from a snap package. The published snap maintains different ``channels`` for different releases of Kubernetes. .. code-block:: bash @@ -110,7 +110,7 @@ Before deploying, run these commands: sudo sysctl fs.inotify.max_user_instances=1280 sudo sysctl fs.inotify.max_user_watches=655360 -We need to run the above because under the hood, microk8s uses inotify to interact with the filesystem, and in large microk8s deployments sometimes the default inotify limits are exceeded. +We need to run the above because under the hood, MicroK8s uses inotify to interact with the filesystem, and in large MicroK8s deployments sometimes the default inotify limits are exceeded. Let's now use Juju to deploy Charmed MLflow. Run the following command: @@ -132,7 +132,7 @@ This will take you to the MLflow UI. .. note:: by default Charmed MLflow creates a `nodeport `_ on port 31380 where you can access the MLflow UI. -That's it! Charmed MLflow has been deployed locally with microk8s and Juju. You can now start using MLflow. +That's it! Charmed MLflow has been deployed locally with MicroK8s and Juju. You can now start using MLflow. Reference: Object storage credentials ------------------------------------- From 17d52abc701291b0a386340428a8b10c76928f1d Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 16:33:09 +0100 Subject: [PATCH 45/64] remove tweaks note --- docs/tutorial/mlflow-kubeflow.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 42b92f2b..65381dc0 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -28,8 +28,6 @@ We’ll also assume that you have a laptop that meets the following conditions: In the remainder of this tutorial, unless otherwise stated, it is assumed you will be running all command line operations on the VM, through the open SSH tunnel. It’s also assumed you’ll be using the web browser on your local machine to access the Kubeflow and MLflow dashboards. -.. note:: With a few tweaks to the instructions, it is possible to do this entire tutorial on a laptop, but running it on a VM will give you a better feel for deploying Kubeflow and MLflow in production. So, we recommend using the VM, even if your laptop is powerful enough to deploy Kubeflow. - Deploy MLflow ------------- From b79c560710e7e33cc51a4c6e000dc8ed973c3a15 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 16:40:24 +0100 Subject: [PATCH 46/64] Patience tweak --- docs/tutorial/mlflow-kubeflow.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 65381dc0..1d4154f1 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -42,8 +42,6 @@ Let's deploy Charmed Kubeflow alongside MLflow. Run the following command to ini juju deploy kubeflow --trust --channel=1.7/stable -Be patient. Kubeflow is large and will take a few minutes to deploy. - Configure Dashboard Access -------------------------- @@ -79,6 +77,9 @@ This will deploy the latest edge version of the dispatcher. See `Resource Dispat juju relate mlflow-server:secrets resource-dispatcher:secrets juju relate mlflow-server:pod-defaults resource-dispatcher:pod-defaults +Monitor The Deployment +---------------------- + Now, at this point, we've deployed MLflow and Kubeflow and we've related them via the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. So, how do you know when all the charms are ready, then? You can do this using the ``juju status`` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: From 466649c11610bd4eb141bf1f4ff7a5f46b08f171 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 16:41:35 +0100 Subject: [PATCH 47/64] remove 5 mins --- docs/tutorial/mlflow-kubeflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 1d4154f1..5b7a29c2 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -98,7 +98,7 @@ The main thing we’re interested in at this stage is the statuses of all the ap This will periodically run a ``juju status`` command and filter to components which are in a state of ``blocked``, ``error``, ``maintenance`` or ``waiting`` i.e. not ``active``. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. -Don’t be surprised if some of the components’ statuses change to ``blocked`` or ``error`` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states for more than 5 minutes or so, consult the troubleshooting steps below. +Don’t be surprised if some of the components’ statuses change to ``blocked`` or ``error`` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states, consult the troubleshooting steps below. .. dropdown:: Expand to troubleshoot: Waiting for gateway relation From b5aed88cbe5a76e32be6aa948de66949dba278df Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Fri, 8 Sep 2023 17:05:30 +0100 Subject: [PATCH 48/64] Some spelling fixes --- docs/.wordlist.txt | 21 +++++++++++++++++++++ docs/tutorial/mlflow-kubeflow.rst | 16 ++++++++-------- docs/tutorial/mlflow.rst | 6 +++--- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 499248c0..f8c23d34 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -1,11 +1,30 @@ +CharmHub +CLI +dropdown Diátaxis +EKS favicon +filesystem +Grafana https installable +Juju +Juju’s +Kubernetes +Kubeflow Makefile +MicroK8s +MinIO +MLOps MyST +namespace +namespaces +NodePort +Observability +OLM Permalink ReadMe +reproducibility readthedocs reST reStructuredText @@ -18,3 +37,5 @@ yaml Jira MLflow mlflow +tensorboard +VM diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 5b7a29c2..74c44ac8 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -14,7 +14,7 @@ Welcome to this tutorial on getting started with Charmed MLflow alongside Charme Prerequisites ------------- -This tutorial assumes you will be deploying Kubeflow and MLflow on a public cloud VM with the following specs: +This tutorial assumes you will be deploying Kubeflow and MLflow on a public cloud Virtual Machine (VM) with the following specs: - Runs Ubuntu 20.04 (focal) or later. - Has at least 4 cores, 32GB RAM and 100GB of disk space available. @@ -31,7 +31,7 @@ In the remainder of this tutorial, unless otherwise stated, it is assumed you wi Deploy MLflow ------------- -Follow the steps in this tutorial to deploy MLflow on your VM: :doc:`mlflow`. Before moving on with this tutorial, confirm that you can now access the MLflow UI on http://localhost:31380. +Follow the steps in this tutorial to deploy MLflow on your VM: :doc:`mlflow`. Before moving on with this tutorial, confirm that you can now access the MLflow UI on ``http://localhost:31380``. Deploy Kubeflow bundle ---------------------- @@ -52,7 +52,7 @@ Run the following commands: juju config dex-auth public-url=http://10.64.140.43.nip.io juju config oidc-gatekeeper public-url=http://10.64.140.43.nip.io -This tells the authentication and authorization components of the bundle that users who access the bundle will be doing so via the URL http://10.64.140.43.nip.io. In turn, this allows those components to construct appropriate responses to incoming traffic. +This tells the authentication and authorisation components of the bundle that users who access the bundle will be doing so via the URL ``http://10.64.140.43.nip.io``. In turn, this allows those components to construct appropriate responses to incoming traffic. Now set the dashboard username and password: @@ -115,7 +115,7 @@ Be patient, it can take up to an hour for all those charms to download and initi Integrate MLflow with Notebook ------------------------------ -In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLFlow in the background. Let's get started. +In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLflow in the background. Let's get started. First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. @@ -140,10 +140,10 @@ At this point, we can name the notebook as we want, and choose the desired image 1. For ``Name``, enter ``test-notebook``. 2. Expand the *Custom Notebook* section and for ``image``, select ``kubeflownotebookswg/jupyter-tensorflow-full:v1.7.0``. -Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to ``Data Volumes -> Advanced options`` and from the ``Configurations`` drop-down, choose the following options: +Now, in order to allow our notebook server access to MLflow, we need to enable some special configuration options. Scroll down to ``Data Volumes -> Advanced options`` and from the ``Configurations`` dropdown, choose the following options: 1. Allow access to Kubeflow pipelines. -2. Allow access to Minio. +2. Allow access to MinIO. 3. Allow access to MLflow. .. note:: Remember we related Kubeflow to MLflow earlier using the resource dispatcher? This is why we're seeing the Minio and MLflow options in the dropdown! @@ -171,9 +171,9 @@ To run MLflow examples on your newly created notebook server, click on the sourc From the menu, choose the ``Clone a Repository`` option. -Now insert this repository address https://github.com/canonical/kubeflow-examples.git +Now insert this repository address ``https://github.com/canonical/kubeflow-examples.git``. -This will clone a whole ``kubeflow-examples`` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that, choose the ``mlflow-v2-examples`` subfolder. +This will clone a whole ``kubeflow-examples`` repository onto the notebook server. The cloned repository will be a folder on the server, with the same name as the remote repository. Go inside the folder and after that, choose the ``mlflow-v2-examples`` sub-folder. There you will find two notebooks: diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index fc852a25..5c6f1e40 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -48,7 +48,7 @@ Enable the following Microk8s add-ons to configure your Kubernetes cluster with microk8s enable dns hostpath-storage ingress metallb:10.64.140.43-10.64.140.49 -Here, we added a DNS service, so the applications can find each other, storage, an ingress controller so we can access Kubeflow components and the MetalLB load balancer application. +Here, we added a ``dns`` service, so the applications can find each other, storage, an ingress controller so we can access Kubeflow components and the ``MetalLB`` load-balancer application. You can see that we added some detail when enabling MetalLB, in this case the address pool to use. > See More : `Microk8s | How to use addons `_ @@ -110,7 +110,7 @@ Before deploying, run these commands: sudo sysctl fs.inotify.max_user_instances=1280 sudo sysctl fs.inotify.max_user_watches=655360 -We need to run the above because under the hood, MicroK8s uses inotify to interact with the filesystem, and in large MicroK8s deployments sometimes the default inotify limits are exceeded. +We need to run the above because under the hood, MicroK8s uses inotify to interact with the filesystem, and in large MicroK8s deployments sometimes the default ``inotify`` limits are exceeded. Let's now use Juju to deploy Charmed MLflow. Run the following command: @@ -136,7 +136,7 @@ That's it! Charmed MLflow has been deployed locally with MicroK8s and Juju. You Reference: Object storage credentials ------------------------------------- -To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with minio. To get the minio credentials run the following command: +To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with MinIO. To get the minio credentials run the following command: .. code-block:: bash From f033bbd64e622423061eb67b470e11fa2e10c812 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:00:03 +0100 Subject: [PATCH 49/64] Spelling --- docs/.wordlist.txt | 12 +++++++++++- docs/how-to/create-ck8s-aws.rst | 2 +- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 2 +- docs/how-to/integrate-cos.rst | 2 +- docs/index.rst | 2 +- docs/tutorial/mlflow-kubeflow.rst | 6 +++--- docs/tutorial/mlflow.rst | 2 +- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index f8c23d34..97285453 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -1,26 +1,36 @@ +addons +API +APIs CharmHub CLI +DEX dropdown Diátaxis +EBS EKS favicon filesystem Grafana https +IAM installable +JSON Juju Juju’s Kubernetes Kubeflow +lifecycle Makefile -MicroK8s +MicroK MinIO +MLflow MLOps MyST namespace namespaces NodePort Observability +OIDC OLM Permalink ReadMe diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index 3672931a..85128106 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -28,7 +28,7 @@ To install some helpful tools, run this command: This installs the following: * ``juju``: Needed to deploy and manage the CK8s cluster. -* ``juju-wait``: CLI tool used for waiting during juju deployments. +* ``juju-wait``: CLI tool used for waiting during Juju deployments. * ``kubectl``: Kubernetes client used to communicate with a Kubernetes cluster. * ``jq``: A lightweight and versatile command-line tool for parsing and manipulating JSON data. diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst index 87d08bfe..fa6ea9bf 100644 --- a/docs/how-to/deploy-mlflow-kubeflow-eks.rst +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -20,7 +20,7 @@ We assume the following: Deploy EKS cluster ------------------- -See our `EKS creation guide `_ for a complete guide on how to do this. **Do not forget** to edit the ``instanceType`` field under ``managedNodeGroups[0].instanceType`` from ``t2.2xlarge`` to ``t3.2xlarge``, as instructed in the guide, since worker nodes of type ``t3.2xlarge`` are required for deploying both MLFlow and Kubeflow. +See our `EKS creation guide `_ for a complete guide on how to do this. **Do not forget** to edit the ``instanceType`` field under ``managedNodeGroups[0].instanceType`` from ``t2.2xlarge`` to ``t3.2xlarge``, as instructed in the guide, since worker nodes of type ``t3.2xlarge`` are required for deploying both MLflow and Kubeflow. Setup Juju ---------- diff --git a/docs/how-to/integrate-cos.rst b/docs/how-to/integrate-cos.rst index 15f7535e..5c9acdaf 100644 --- a/docs/how-to/integrate-cos.rst +++ b/docs/how-to/integrate-cos.rst @@ -6,7 +6,7 @@ This guide shows how to integrate MLflow with the Canonical Observability Stack Prerequisites ------------- -This guide asssumes: +This guide assumes: #. You have deployed the COS stack in the ``cos`` model. For steps on how to do this, see the `MicroK8s tutorial `_. #. You have deployed the MLflow bundle in the ``kubeflow`` model. For steps on how to do this, see :doc:`../tutorial/mlflow`. diff --git a/docs/index.rst b/docs/index.rst index 4a1782ed..45109f06 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ It provides tools for tracking experiments, packaging code into reproducible run It addresses a number of challenges facing machine learning teams: collaboration, reproducibility, maintenance, organisation and scaling. -It's ideal for data scientists, ML engineers, hobbyists and teams looking to optimize their ML workflows with charms. +It's ideal for data scientists, ML engineers, hobbyists and teams looking to optimise their ML workflows with charms. --------- diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 74c44ac8..201dd1db 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -117,7 +117,7 @@ Integrate MLflow with Notebook In this section, we're going to create a notebook server in Kubeflow and connect it to MLflow. This will allow our notebook logic to talk to MLflow in the background. Let's get started. -First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at http://10.64.140.43.nip.io/ and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. +First, to be able to use MLflow credentials in your Kubeflow notebook, visit the dashboard at ``http://10.64.140.43.nip.io/`` and fill the username and password which you configured in the previous section e.g. ``user123@email.com`` and ``user123``. Click on start setup to setup the Kubeflow user for the first time. @@ -129,7 +129,7 @@ Now a Kubernetes namespace was created for your user. To use MLflow with this us microk8s kubectl label ns user123 user.kubeflow.org/enabled="true" -You will get the following output: `namespace/user123 labeled`. +You will get the following output: ``namespace/user123 labeled``. For more info on the label command, check `Kubernetes labels `_. For more info on Kubernetes namespaces for users, see the `upstream docs on Multi-user isolation `_. @@ -146,7 +146,7 @@ Now, in order to allow our notebook server access to MLflow, we need to enable s 2. Allow access to MinIO. 3. Allow access to MLflow. -.. note:: Remember we related Kubeflow to MLflow earlier using the resource dispatcher? This is why we're seeing the Minio and MLflow options in the dropdown! +.. note:: Remember we related Kubeflow to MLflow earlier using the resource dispatcher? This is why we're seeing the MinIO and MLflow options in the dropdown! Great, that's all the configuration for the notebook server done. Hit the Launch button to launch the notebook server. Be patient, the notebook server will take a little while to initialise. diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index 5c6f1e40..c45f520e 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -42,7 +42,7 @@ It is also useful to make sure the user has the proper access and ownership of a sudo chown -f -R $USER ~/.kube -Enable the following Microk8s add-ons to configure your Kubernetes cluster with extra services needed to run Charmed Kubeflow. +Enable the following Microk8s addons to configure your Kubernetes cluster with extra services needed to run Charmed Kubeflow. .. code-block:: bash From 128b02904825debc34288bce303a774fc1798cc6 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:11:24 +0100 Subject: [PATCH 50/64] Final spell check --- docs/.wordlist.txt | 3 ++- docs/how-to/deploy-eks.rst | 2 +- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 4 ++-- docs/tutorial/mlflow.rst | 16 ++++++++-------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 97285453..fcc706ba 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -1,6 +1,7 @@ addons API APIs +balancer CharmHub CLI DEX @@ -11,12 +12,12 @@ EKS favicon filesystem Grafana +hostname https IAM installable JSON Juju -Juju’s Kubernetes Kubeflow lifecycle diff --git a/docs/how-to/deploy-eks.rst b/docs/how-to/deploy-eks.rst index eec686da..93ad4a11 100644 --- a/docs/how-to/deploy-eks.rst +++ b/docs/how-to/deploy-eks.rst @@ -45,7 +45,7 @@ Create a controller: .. note:: You can use whatever controller name you like here, we chose ``kubeflow-controller``. -Add a juju model: +Add a Juju model: .. code-block:: bash diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst index fa6ea9bf..3dc55c5f 100644 --- a/docs/how-to/deploy-mlflow-kubeflow-eks.rst +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -25,7 +25,7 @@ See our `EKS creation guide `_ is an So wait, what does "Charmed MLflow" mean? Is it the same thing as MLflow? Yes and no. MLflow is a complex application, consisting of many components running together and communicating with each other. Charmed MLflow is a `charm bundle `_ that allows us to deploy MLflow quickly and easily. Don't worry too much about what a "charm bundle" is right now. The key thing is that it's going to make deploying MLflow very convenient for us: we'll get MLflow up and running with just a few command line commands! -In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the CLI tool `juju `_, we'll deploy MLflow to a local `MicroK8s `_ cloud. +In this tutorial, we're going to explore Charmed MLflow in a practical way. Using the `Juju `_ CLI tool, we'll deploy MLflow to a local `MicroK8s `_ cloud. Prerequisites ------------- @@ -42,16 +42,16 @@ It is also useful to make sure the user has the proper access and ownership of a sudo chown -f -R $USER ~/.kube -Enable the following Microk8s addons to configure your Kubernetes cluster with extra services needed to run Charmed Kubeflow. +Enable the following MicroK8s addons to configure your Kubernetes cluster with extra services needed to run Charmed Kubeflow. .. code-block:: bash microk8s enable dns hostpath-storage ingress metallb:10.64.140.43-10.64.140.49 Here, we added a ``dns`` service, so the applications can find each other, storage, an ingress controller so we can access Kubeflow components and the ``MetalLB`` load-balancer application. -You can see that we added some detail when enabling MetalLB, in this case the address pool to use. +You can see that we added some detail when enabling ``MetalLB``, in this case the address pool to use. -> See More : `Microk8s | How to use addons `_ +> See More : `MicroK8s | How to use addons `_ We've now installed and configured MicroK8s. It will start running automatically, but can take 5 minutes or so before it's ready for action. Run the following command to tell MicroK8s to report its status to us when it's ready: @@ -87,7 +87,7 @@ Now, run the following command to deploy a Juju controller to the Kubernetes we Sit tight while the command completes! The controller may take a minute or two to deploy. -The controller is Juju’s agent, running on Kubernetes, which can be used to deploy and control the components of Kubeflow. +The controller is the agent of Juju, running on Kubernetes, which can be used to deploy and control the components of Kubeflow. Next, we'll need to add a model for Kubeflow to the controller. Run the following command to add a model called ``kubeflow``: @@ -110,7 +110,7 @@ Before deploying, run these commands: sudo sysctl fs.inotify.max_user_instances=1280 sudo sysctl fs.inotify.max_user_watches=655360 -We need to run the above because under the hood, MicroK8s uses inotify to interact with the filesystem, and in large MicroK8s deployments sometimes the default ``inotify`` limits are exceeded. +We need to run the above because under the hood, MicroK8s uses ``inotify`` to interact with the filesystem, and in large MicroK8s deployments sometimes the default ``inotify`` limits are exceeded. Let's now use Juju to deploy Charmed MLflow. Run the following command: @@ -130,13 +130,13 @@ To access MLflow, visit the following URL in your web browser: This will take you to the MLflow UI. -.. note:: by default Charmed MLflow creates a `nodeport `_ on port 31380 where you can access the MLflow UI. +.. note:: by default Charmed MLflow creates a `NodePort `_ on port 31380 where you can access the MLflow UI. That's it! Charmed MLflow has been deployed locally with MicroK8s and Juju. You can now start using MLflow. Reference: Object storage credentials ------------------------------------- -To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with MinIO. To get the minio credentials run the following command: +To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with MinIO. To get the ``MinIO`` credentials run the following command: .. code-block:: bash From 3ce6064e014060020a0ddd714a5c825462374d7c Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:20:30 +0100 Subject: [PATCH 51/64] wordlist update --- docs/.wordlist.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index fcc706ba..3bc6c17a 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -42,9 +42,7 @@ reStructuredText RTD subdirectories subtree -txt UI -yaml Jira MLflow mlflow From 240d2a09674605f1a296c8dfe63d619942d506bf Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:21:35 +0100 Subject: [PATCH 52/64] wordlist update --- docs/.wordlist.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 3bc6c17a..707d729f 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -30,7 +30,7 @@ MyST namespace namespaces NodePort -Observability +observability OIDC OLM Permalink From 21bd32d49cec764d7154a79f52d45c9cc73f4baa Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:29:45 +0100 Subject: [PATCH 53/64] wordlist --- docs/.wordlist.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index 707d729f..bac2ab4b 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -13,7 +13,6 @@ favicon filesystem Grafana hostname -https IAM installable JSON From 43b8f4a4f9011da7667d66c868a01e4e7350a7db Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:34:17 +0100 Subject: [PATCH 54/64] Prereq sect --- docs/how-to/deploy-ck8s-aws.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index e17da187..39f922c3 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -9,7 +9,9 @@ Deploy Charmed MLflow to Charmed Kubernetes on AWS This guide shows how to connect Juju to an existing `Charmed Kubernetes `_ (CK8s) cluster and deploy the MLflow bundle on top of it. -**Prerequisites:** +Prerequisites +------------- + We assume that you have access to a CK8s cluster using ``kubectl``. If you don't have a cluster set up, you can follow this guide: `Create CK8s on AWS <../create-ck8s-aws>`_. Install Juju From c2f35b5f01cd179226a129dea627483ea342a86e Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Mon, 11 Sep 2023 10:43:09 +0100 Subject: [PATCH 55/64] Fix links --- docs/how-to/create-ck8s-aws.rst | 4 ++-- docs/how-to/deploy-ck8s-aws.rst | 2 +- docs/how-to/integrate-ml-ckf-ck8s.rst | 4 ++-- docs/tutorial/mlflow-kubeflow.rst | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/how-to/create-ck8s-aws.rst b/docs/how-to/create-ck8s-aws.rst index 85128106..15a710a6 100644 --- a/docs/how-to/create-ck8s-aws.rst +++ b/docs/how-to/create-ck8s-aws.rst @@ -57,11 +57,11 @@ Bootstrap a Juju controller that will be responsible for deploying cluster appli Deploy Charmed Kubernetes 1.24 ------------------------------ -Clone the `Charmed Kubernetes bundle repository `_, and update CPU, disk, and memory constraints to meet Kubeflow requirements. +Clone the `Charmed Kubernetes bundle repository `_, and update CPU, disk, and memory constraints to meet Kubeflow requirements. .. code-block:: bash - git clone https://github.com/charmed-kubernetes/bundle.git + git clone https://github.com/charmed-kubernetes/bundle sed -i '/^ *charm: kubernetes-worker/,/^ *[^:]*:/s/constraints: cores=2 mem=8G root-disk=16G/constraints: cores=8 mem=32G root-disk=200G/' ./bundle/releases/1.24/bundle.yaml Deploy the Charmed Kubernetes bundle on AWS with the storage overlay. This overlay enables you to create Kubernetes volumes backed by AWS EBS. diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 39f922c3..57b48b37 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -12,7 +12,7 @@ This guide shows how to connect Juju to an existing `Charmed Kubernetes `_. +We assume that you have access to a CK8s cluster using ``kubectl``. If you don't have a cluster set up, you can follow this guide: :doc:`Create CK8s on AWS `. Install Juju ------------ diff --git a/docs/how-to/integrate-ml-ckf-ck8s.rst b/docs/how-to/integrate-ml-ckf-ck8s.rst index fbd6f89a..bf008aa4 100644 --- a/docs/how-to/integrate-ml-ckf-ck8s.rst +++ b/docs/how-to/integrate-ml-ckf-ck8s.rst @@ -13,9 +13,9 @@ Prerequisites -------------- We assume that: -* You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow the `creation guide <../create-ck8s-aws>`_ to deploy one on AWS. +* You have access to a Charmed Kubernetes cluster using ``kubectl``. If you don't have a cluster set up, you can follow the :doc:`creation guide ` to deploy one on AWS. * You have deployed the Charmed Kubeflow bundle. If you don't have it, here is `a guide `_ on how to do it. -* You have deployed the Charmed MLflow bundle. To see how, follow our `deployment guide <../deploy-ck8s-aws>`_. +* You have deployed the Charmed MLflow bundle. To see how, follow our :doc:`deployment guide `. Deploy resource dispatcher -------------------------- diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 201dd1db..2f74316a 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -88,7 +88,7 @@ So, how do you know when all the charms are ready, then? You can do this using t juju status -Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don’t worry too much about what this all means for now. If you’re interested in learning more about this command and its output, see the `Juju Status command `_. +Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don’t worry too much about what this all means for now. If you’re interested in learning more about this command and its output, see the `Juju Status command `_. The main thing we’re interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become ``active``, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: @@ -110,7 +110,7 @@ Don’t be surprised if some of the components’ statuses change to ``blocked`` This is a known issue, see `tensorboard-controller GitHub issue `_ for more info. -Be patient, it can take up to an hour for all those charms to download and initialise. In the meantime, why not try our `Juju tutorial `_? +Be patient, it can take up to an hour for all those charms to download and initialise. In the meantime, why not try our `Juju tutorial `_? Integrate MLflow with Notebook ------------------------------ From 64b9655b651ff1e7219849e53b6daf65ce1e803c Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 13:19:52 +0100 Subject: [PATCH 56/64] Published versions MLflow and resource dispatcher Use published versions --- docs/how-to/deploy-ck8s-aws.rst | 2 +- docs/how-to/deploy-eks.rst | 2 +- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 4 ++-- docs/how-to/integrate-ml-ckf-ck8s.rst | 2 +- docs/tutorial/mlflow-kubeflow.rst | 2 +- docs/tutorial/mlflow.rst | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 57b48b37..8a01d2ca 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -46,7 +46,7 @@ Deploy the MLflow bundle: .. code-block:: bash - juju deploy mlflow --channel=2.1/edge --trust + juju deploy mlflow --channel=2.1/stable --trust Wait until the deployments are active: diff --git a/docs/how-to/deploy-eks.rst b/docs/how-to/deploy-eks.rst index 93ad4a11..34a60795 100644 --- a/docs/how-to/deploy-eks.rst +++ b/docs/how-to/deploy-eks.rst @@ -59,7 +59,7 @@ Deploy the MLflow bundle with the following command: .. code-block:: bash - juju deploy mlflow --channel=2.1/edge --trust + juju deploy mlflow --channel=2.1/stable --trust Wait until all charms are in the active state. You can check the state of the charms with the command: diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst index 3dc55c5f..1623194b 100644 --- a/docs/how-to/deploy-mlflow-kubeflow-eks.rst +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -58,7 +58,7 @@ Deploy the MLflow bundle with the following command: .. code-block:: bash - juju deploy mlflow --channel=2.1/edge --trust + juju deploy mlflow --channel=2.1/stable --trust Wait until all charms are in the active state. You can check the state of the charms with the command: @@ -88,7 +88,7 @@ The resource dispatcher is used to connect MLflow with Kubeflow. In particular, .. code-block:: bash - juju deploy resource-dispatcher --channel edge --trust + juju deploy resource-dispatcher --channel 1.0/stabe --trust Relate the resource dispatcher to MLflow with the following commands: diff --git a/docs/how-to/integrate-ml-ckf-ck8s.rst b/docs/how-to/integrate-ml-ckf-ck8s.rst index bf008aa4..143394bd 100644 --- a/docs/how-to/integrate-ml-ckf-ck8s.rst +++ b/docs/how-to/integrate-ml-ckf-ck8s.rst @@ -24,7 +24,7 @@ Deploy the resource dispatcher: .. code-block:: bash - juju deploy resource-dispatcher --trust + juju deploy resource-dispatcher --channel 1.0/stabe --trust Relate Resource dispatcher to MLflow ------------------------------------ diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 2f74316a..2664a110 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -68,7 +68,7 @@ Next, let's deploy the resource dispatcher. The resource dispatcher is an option .. code-block:: bash - juju deploy resource-dispatcher --channel edge --trust + juju deploy resource-dispatcher --channel 1.0/stabe --trust This will deploy the latest edge version of the dispatcher. See `Resource Dispatcher on GitHub `_ for more info. Now we must relate the dispatcher to MLflow: diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index 147552b8..c5582ebd 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -116,7 +116,7 @@ Let's now use Juju to deploy Charmed MLflow. Run the following command: .. code-block:: bash - juju deploy mlflow --channel=2.1/edge --trust + juju deploy mlflow --channel=2.1/stable --trust This deploys the latest edge version of MLflow with `MinIO `_ as object storage and `MySQL `_ as metadata store. From cd45c2aa30455f46a5f1d9d067d72056d3bb1a2d Mon Sep 17 00:00:00 2001 From: Colm Bhandal Date: Thu, 14 Sep 2023 14:15:44 +0100 Subject: [PATCH 57/64] Update docs/how-to/deploy-ck8s-aws.rst Co-authored-by: Phoevos Kalemkeris --- docs/how-to/deploy-ck8s-aws.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/how-to/deploy-ck8s-aws.rst b/docs/how-to/deploy-ck8s-aws.rst index 8a01d2ca..eba1aed5 100644 --- a/docs/how-to/deploy-ck8s-aws.rst +++ b/docs/how-to/deploy-ck8s-aws.rst @@ -1,11 +1,11 @@ Deploy Charmed MLflow to Charmed Kubernetes on AWS ======================================================== -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ This guide shows how to connect Juju to an existing `Charmed Kubernetes `_ (CK8s) cluster and deploy the MLflow bundle on top of it. From c295b6b7153f1f5a9f5c112c8dd0df939c547d4d Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 14:20:00 +0100 Subject: [PATCH 58/64] Version tables rendering --- docs/how-to/deploy-eks.rst | 10 +++++----- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 10 +++++----- docs/how-to/integrate-ml-ckf-ck8s.rst | 10 +++++----- docs/tutorial/mlflow-kubeflow.rst | 14 +++++++------- docs/tutorial/mlflow.rst | 10 +++++----- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/how-to/deploy-eks.rst b/docs/how-to/deploy-eks.rst index 34a60795..80a2ddd4 100644 --- a/docs/how-to/deploy-eks.rst +++ b/docs/how-to/deploy-eks.rst @@ -1,11 +1,11 @@ Deploy Charmed MLflow to EKS ============================ -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ This guide shows how to deploy Charmed MLflow on `AWS Elastic Kubernetes Service `_ (EKS). In this guide, we will create an AWS EKS cluster, connect Juju to it, and deploy the MLflow bundle. diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst index 1623194b..2e0026b3 100644 --- a/docs/how-to/deploy-mlflow-kubeflow-eks.rst +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -1,11 +1,11 @@ Deploy Charmed MLflow and Kubeflow to EKS ========================================= -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ This guide shows how to deploy Charmed MLflow alongside Kubeflow on `AWS Elastic Kubernetes Service `_ (EKS). In this guide, we will create an AWS EKS cluster, connect Juju to it, deploy the MLflow and Kubeflow bundles, and relate them to each other. diff --git a/docs/how-to/integrate-ml-ckf-ck8s.rst b/docs/how-to/integrate-ml-ckf-ck8s.rst index 143394bd..59f43750 100644 --- a/docs/how-to/integrate-ml-ckf-ck8s.rst +++ b/docs/how-to/integrate-ml-ckf-ck8s.rst @@ -1,11 +1,11 @@ Integrate Charmed MLflow with Charmed Kubeflow on Charmed Kubernetes ========================================================================= -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ In this guide, we will guide you through the process of integrating Charmed MLflow with Charmed Kubeflow on `Charmed Kubernetes `_. diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 2664a110..78238830 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -1,13 +1,13 @@ Getting Started with Charmed MLflow and Kubeflow ================================================ -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ -| Kubeflow | 1.7 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ +| Kubeflow | 1.7 | ++-----------+---------+ Welcome to this tutorial on getting started with Charmed MLflow alongside Charmed Kubeflow. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index c5582ebd..11613990 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -1,11 +1,11 @@ Get Started with Charmed MLflow ================================== -+------------+---------+ -| Component | Version | -+============+=========+ -| MLflow | 2 | -+------------+---------+ ++-----------+---------+ +| Component | Version | ++-----------+---------+ +| MLflow | 2 | ++-----------+---------+ Welcome to the tutorial on Charmed MLflow! `MLflow `_ is an open-source platform, used for managing machine learning workflows. It has four primary functions that include experiment tracking, model registry, model management and code reproducibility. From b716d0b02f5c09595c30b27562b1432413aca652 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:16:44 +0100 Subject: [PATCH 59/64] TensorBoard --- docs/.wordlist.txt | 2 +- docs/tutorial/mlflow-kubeflow.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index bac2ab4b..fbd05823 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -45,5 +45,5 @@ UI Jira MLflow mlflow -tensorboard +TensorBoard VM diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 78238830..080f31bb 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -108,7 +108,7 @@ Don’t be surprised if some of the components’ statuses change to ``blocked`` juju run --unit istio-pilot/0 -- "export JUJU_DISPATCH_PATH=hooks/config-changed; ./dispatch" - This is a known issue, see `tensorboard-controller GitHub issue `_ for more info. + This is a known issue, see `TensorBoard controller GitHub issue `_ for more info. Be patient, it can take up to an hour for all those charms to download and initialise. In the meantime, why not try our `Juju tutorial `_? From 079df492d7be9488fe60648efd88ecdd3f92f63b Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:21:10 +0100 Subject: [PATCH 60/64] Wordlist spellcheck Alphbabetically ordered Removed mlflow lowercase Fixed spelling issue --- docs/.wordlist.txt | 10 ++++------ docs/tutorial/mlflow.rst | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/.wordlist.txt b/docs/.wordlist.txt index fbd05823..5fb54827 100644 --- a/docs/.wordlist.txt +++ b/docs/.wordlist.txt @@ -5,8 +5,8 @@ balancer CharmHub CLI DEX -dropdown Diátaxis +dropdown EBS EKS favicon @@ -15,10 +15,11 @@ Grafana hostname IAM installable +Jira JSON Juju -Kubernetes Kubeflow +Kubernetes lifecycle Makefile MicroK @@ -34,16 +35,13 @@ OIDC OLM Permalink ReadMe -reproducibility readthedocs +reproducibility reST reStructuredText RTD subdirectories subtree UI -Jira -MLflow -mlflow TensorBoard VM diff --git a/docs/tutorial/mlflow.rst b/docs/tutorial/mlflow.rst index 11613990..2f48c588 100644 --- a/docs/tutorial/mlflow.rst +++ b/docs/tutorial/mlflow.rst @@ -136,7 +136,7 @@ That's it! Charmed MLflow has been deployed locally with MicroK8s and Juju. You Reference: Object storage credentials ------------------------------------- -To use mlflow you need to have credentials to the object storage. The aforementioned bundle comes with MinIO. To get the ``MinIO`` credentials run the following command: +To use MLflow you need to have credentials to the object storage. The aforementioned bundle comes with MinIO. To get the ``MinIO`` credentials run the following command: .. code-block:: bash From 5711feb67cb01e34f2e428b9adb4299daf10f0e1 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:26:24 +0100 Subject: [PATCH 61/64] stabe typo --- docs/how-to/deploy-mlflow-kubeflow-eks.rst | 2 +- docs/how-to/integrate-ml-ckf-ck8s.rst | 2 +- docs/tutorial/mlflow-kubeflow.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how-to/deploy-mlflow-kubeflow-eks.rst b/docs/how-to/deploy-mlflow-kubeflow-eks.rst index 2e0026b3..bf280e40 100644 --- a/docs/how-to/deploy-mlflow-kubeflow-eks.rst +++ b/docs/how-to/deploy-mlflow-kubeflow-eks.rst @@ -88,7 +88,7 @@ The resource dispatcher is used to connect MLflow with Kubeflow. In particular, .. code-block:: bash - juju deploy resource-dispatcher --channel 1.0/stabe --trust + juju deploy resource-dispatcher --channel 1.0/stable --trust Relate the resource dispatcher to MLflow with the following commands: diff --git a/docs/how-to/integrate-ml-ckf-ck8s.rst b/docs/how-to/integrate-ml-ckf-ck8s.rst index 59f43750..7d2ed589 100644 --- a/docs/how-to/integrate-ml-ckf-ck8s.rst +++ b/docs/how-to/integrate-ml-ckf-ck8s.rst @@ -24,7 +24,7 @@ Deploy the resource dispatcher: .. code-block:: bash - juju deploy resource-dispatcher --channel 1.0/stabe --trust + juju deploy resource-dispatcher --channel 1.0/stable --trust Relate Resource dispatcher to MLflow ------------------------------------ diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 080f31bb..2a13106e 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -68,7 +68,7 @@ Next, let's deploy the resource dispatcher. The resource dispatcher is an option .. code-block:: bash - juju deploy resource-dispatcher --channel 1.0/stabe --trust + juju deploy resource-dispatcher --channel 1.0/stable --trust This will deploy the latest edge version of the dispatcher. See `Resource Dispatcher on GitHub `_ for more info. Now we must relate the dispatcher to MLflow: From af8d053b31b8b6199108396690aa8122711977ca Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:33:32 +0100 Subject: [PATCH 62/64] Table spacing --- docs/tutorial/mlflow-kubeflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index 2a13106e..ac85f481 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -6,7 +6,7 @@ Getting Started with Charmed MLflow and Kubeflow +-----------+---------+ | MLflow | 2 | +-----------+---------+ -| Kubeflow | 1.7 | +| Kubeflow | 1.7 | +-----------+---------+ Welcome to this tutorial on getting started with Charmed MLflow alongside Charmed Kubeflow. If you would like to deploy Kubeflow by itself, see our tutorial on `Charmed Kubeflow `_. From bc4277f31c255ffdf0a59a7041fe150b6450e3ec Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:38:13 +0100 Subject: [PATCH 63/64] reword --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 45109f06..f35b2535 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,7 @@ Charmed MLflow is a platform for managing the end-to-end machine learning lifecy It provides tools for tracking experiments, packaging code into reproducible runs, and sharing and deploying models. It integrates with popular machine learning frameworks. -It addresses a number of challenges facing machine learning teams: collaboration, reproducibility, maintenance, organisation and scaling. +It addresses a number of common machine learning challenges: collaboration, reproducibility, maintenance, organisation and scaling. It's ideal for data scientists, ML engineers, hobbyists and teams looking to optimise their ML workflows with charms. From 2fee4f0cb9862062d0361b3e73bc8e606e172000 Mon Sep 17 00:00:00 2001 From: ColmBhandal Date: Thu, 14 Sep 2023 16:51:23 +0100 Subject: [PATCH 64/64] Correct apostrophe --- docs/tutorial/mlflow-kubeflow.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/mlflow-kubeflow.rst b/docs/tutorial/mlflow-kubeflow.rst index ac85f481..b7c9b4c0 100644 --- a/docs/tutorial/mlflow-kubeflow.rst +++ b/docs/tutorial/mlflow-kubeflow.rst @@ -20,13 +20,13 @@ This tutorial assumes you will be deploying Kubeflow and MLflow on a public clou - Has at least 4 cores, 32GB RAM and 100GB of disk space available. - Is connected to the internet for downloading the required snaps and charms. -We’ll also assume that you have a laptop that meets the following conditions: +We'll also assume that you have a laptop that meets the following conditions: - Has an SSH tunnel open to the VM with port forwarding and a SOCKS proxy. To see how to set this up, see `How to setup SSH VM Access `_. - Runs Ubuntu 20.04 (focal) or later. - Has a web browser installed e.g. Chrome / Firefox / Edge. -In the remainder of this tutorial, unless otherwise stated, it is assumed you will be running all command line operations on the VM, through the open SSH tunnel. It’s also assumed you’ll be using the web browser on your local machine to access the Kubeflow and MLflow dashboards. +In the remainder of this tutorial, unless otherwise stated, it is assumed you will be running all command line operations on the VM, through the open SSH tunnel. It's also assumed you'll be using the web browser on your local machine to access the Kubeflow and MLflow dashboards. Deploy MLflow ------------- @@ -82,15 +82,15 @@ Monitor The Deployment Now, at this point, we've deployed MLflow and Kubeflow and we've related them via the resource dispatcher. But that doesn't mean our system is ready yet: Juju will need to download charm data from CharmHub and the charms themselves will take some time to initialise. -So, how do you know when all the charms are ready, then? You can do this using the ``juju status`` command. First, let’s run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: +So, how do you know when all the charms are ready, then? You can do this using the ``juju status`` command. First, let's run a basic status command and review the output. Run the following command to print out the status of all the components of Juju: .. code-block:: bash juju status -Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don’t worry too much about what this all means for now. If you’re interested in learning more about this command and its output, see the `Juju Status command `_. +Review the output for yourself. You should see some summary information, a list of Apps and associated information, and another list of Units and their associated information. Don't worry too much about what this all means for now. If you're interested in learning more about this command and its output, see the `Juju Status command `_. -The main thing we’re interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become ``active``, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: +The main thing we're interested in at this stage is the statuses of all the applications and units running through Juju. We want all the statuses to eventually become ``active``, indicating that the bundle is ready. Run the following command to keep a watch on the components which are not active yet: .. code-block:: bash @@ -98,7 +98,7 @@ The main thing we’re interested in at this stage is the statuses of all the ap This will periodically run a ``juju status`` command and filter to components which are in a state of ``blocked``, ``error``, ``maintenance`` or ``waiting`` i.e. not ``active``. When this output becomes empty except for the “App” and “Unit” headings, then we know all statuses are active and our system is ready. -Don’t be surprised if some of the components’ statuses change to ``blocked`` or ``error`` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states, consult the troubleshooting steps below. +Don't be surprised if some of the components' statuses change to ``blocked`` or ``error`` every now and then. This is expected behaviour, and these statuses should resolve by themselves as the bundle configures itself. However, if components remain stuck in the same error states, consult the troubleshooting steps below. .. dropdown:: Expand to troubleshoot: Waiting for gateway relation