Showing preview only (1,631K chars total). Download the full file or copy to clipboard to get everything.
Repository: mlops-for-all/mlops-for-all.github.io
Branch: main
Commit: fc01e7722ed3
Files: 291
Total size: 1.3 MB
Directory structure:
gitextract_cdatvxxx/
├── .github/
│ ├── CODEOWNERS
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── deploy.yml
│ └── pull-request.yml
├── .gitignore
├── README.md
├── babel.config.js
├── community/
│ ├── community.md
│ ├── contributors.md
│ └── how-to-contribute.md
├── docs/
│ ├── api-deployment/
│ │ ├── _category_.json
│ │ ├── seldon-children.md
│ │ ├── seldon-fields.md
│ │ ├── seldon-iris.md
│ │ ├── seldon-mlflow.md
│ │ ├── seldon-pg.md
│ │ └── what-is-api-deployment.md
│ ├── appendix/
│ │ ├── _category_.json
│ │ ├── metallb.md
│ │ └── pyenv.md
│ ├── further-readings/
│ │ ├── _category_.json
│ │ └── info.md
│ ├── introduction/
│ │ ├── _category_.json
│ │ ├── component.md
│ │ ├── intro.md
│ │ ├── levels.md
│ │ └── why_kubernetes.md
│ ├── kubeflow/
│ │ ├── _category_.json
│ │ ├── advanced-component.md
│ │ ├── advanced-environment.md
│ │ ├── advanced-mlflow.md
│ │ ├── advanced-pipeline.md
│ │ ├── advanced-run.md
│ │ ├── basic-component.md
│ │ ├── basic-pipeline-upload.md
│ │ ├── basic-pipeline.md
│ │ ├── basic-requirements.md
│ │ ├── basic-run.md
│ │ ├── how-to-debug.md
│ │ ├── kubeflow-concepts.md
│ │ └── kubeflow-intro.md
│ ├── kubeflow-dashboard-guide/
│ │ ├── _category_.json
│ │ ├── experiments-and-others.md
│ │ ├── experiments.md
│ │ ├── intro.md
│ │ ├── notebooks.md
│ │ ├── tensorboards.md
│ │ └── volumes.md
│ ├── prerequisites/
│ │ ├── _category_.json
│ │ └── docker/
│ │ ├── _category_.json
│ │ ├── advanced.md
│ │ ├── command.md
│ │ ├── docker.md
│ │ ├── images.md
│ │ ├── install.md
│ │ └── introduction.md
│ ├── setup-components/
│ │ ├── _category_.json
│ │ ├── install-components-kf.md
│ │ ├── install-components-mlflow.md
│ │ ├── install-components-pg.md
│ │ └── install-components-seldon.md
│ └── setup-kubernetes/
│ ├── _category_.json
│ ├── install-kubernetes/
│ │ ├── _category_.json
│ │ ├── kubernetes-with-k3s.md
│ │ ├── kubernetes-with-kubeadm.md
│ │ └── kubernetes-with-minikube.md
│ ├── install-kubernetes-module.md
│ ├── install-prerequisite.md
│ ├── intro.md
│ ├── kubernetes.md
│ └── setup-nvidia-gpu.md
├── docusaurus.config.js
├── i18n/
│ ├── en/
│ │ ├── code.json
│ │ ├── docusaurus-plugin-content-blog/
│ │ │ └── options.json
│ │ ├── docusaurus-plugin-content-docs/
│ │ │ ├── current/
│ │ │ │ ├── api-deployment/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── seldon-children.md
│ │ │ │ │ ├── seldon-fields.md
│ │ │ │ │ ├── seldon-iris.md
│ │ │ │ │ ├── seldon-mlflow.md
│ │ │ │ │ ├── seldon-pg.md
│ │ │ │ │ └── what-is-api-deployment.md
│ │ │ │ ├── appendix/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── metallb.md
│ │ │ │ │ └── pyenv.md
│ │ │ │ ├── further-readings/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ └── info.md
│ │ │ │ ├── introduction/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── component.md
│ │ │ │ │ ├── intro.md
│ │ │ │ │ ├── levels.md
│ │ │ │ │ └── why_kubernetes.md
│ │ │ │ ├── kubeflow/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── advanced-component.md
│ │ │ │ │ ├── advanced-environment.md
│ │ │ │ │ ├── advanced-mlflow.md
│ │ │ │ │ ├── advanced-pipeline.md
│ │ │ │ │ ├── advanced-run.md
│ │ │ │ │ ├── basic-component.md
│ │ │ │ │ ├── basic-pipeline-upload.md
│ │ │ │ │ ├── basic-pipeline.md
│ │ │ │ │ ├── basic-requirements.md
│ │ │ │ │ ├── basic-run.md
│ │ │ │ │ ├── how-to-debug.md
│ │ │ │ │ ├── kubeflow-concepts.md
│ │ │ │ │ └── kubeflow-intro.md
│ │ │ │ ├── kubeflow-dashboard-guide/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── experiments-and-others.md
│ │ │ │ │ ├── experiments.md
│ │ │ │ │ ├── intro.md
│ │ │ │ │ ├── notebooks.md
│ │ │ │ │ ├── tensorboards.md
│ │ │ │ │ └── volumes.md
│ │ │ │ ├── prerequisites/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ └── docker/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── advanced.md
│ │ │ │ │ ├── command.md
│ │ │ │ │ ├── docker.md
│ │ │ │ │ ├── images.md
│ │ │ │ │ ├── install.md
│ │ │ │ │ └── introduction.md
│ │ │ │ ├── setup-components/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── install-components-kf.md
│ │ │ │ │ ├── install-components-mlflow.md
│ │ │ │ │ ├── install-components-pg.md
│ │ │ │ │ └── install-components-seldon.md
│ │ │ │ └── setup-kubernetes/
│ │ │ │ ├── _category_.json
│ │ │ │ ├── install-kubernetes/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── kubernetes-with-k3s.md
│ │ │ │ │ ├── kubernetes-with-kubeadm.md
│ │ │ │ │ └── kubernetes-with-minikube.md
│ │ │ │ ├── install-kubernetes-module.md
│ │ │ │ ├── install-prerequisite.md
│ │ │ │ ├── intro.md
│ │ │ │ ├── kubernetes.md
│ │ │ │ └── setup-nvidia-gpu.md
│ │ │ ├── current.json
│ │ │ ├── version-1.0/
│ │ │ │ ├── api-deployment/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── seldon-children.md
│ │ │ │ │ ├── seldon-fields.md
│ │ │ │ │ ├── seldon-iris.md
│ │ │ │ │ ├── seldon-mlflow.md
│ │ │ │ │ ├── seldon-pg.md
│ │ │ │ │ └── what-is-api-deployment.md
│ │ │ │ ├── appendix/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── metallb.md
│ │ │ │ │ └── pyenv.md
│ │ │ │ ├── further-readings/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ └── info.md
│ │ │ │ ├── introduction/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── component.md
│ │ │ │ │ ├── intro.md
│ │ │ │ │ ├── levels.md
│ │ │ │ │ └── why_kubernetes.md
│ │ │ │ ├── kubeflow/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── advanced-component.md
│ │ │ │ │ ├── advanced-environment.md
│ │ │ │ │ ├── advanced-mlflow.md
│ │ │ │ │ ├── advanced-pipeline.md
│ │ │ │ │ ├── advanced-run.md
│ │ │ │ │ ├── basic-component.md
│ │ │ │ │ ├── basic-pipeline-upload.md
│ │ │ │ │ ├── basic-pipeline.md
│ │ │ │ │ ├── basic-requirements.md
│ │ │ │ │ ├── basic-run.md
│ │ │ │ │ ├── how-to-debug.md
│ │ │ │ │ ├── kubeflow-concepts.md
│ │ │ │ │ └── kubeflow-intro.md
│ │ │ │ ├── kubeflow-dashboard-guide/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── experiments-and-others.md
│ │ │ │ │ ├── experiments.md
│ │ │ │ │ ├── intro.md
│ │ │ │ │ ├── notebooks.md
│ │ │ │ │ ├── tensorboards.md
│ │ │ │ │ └── volumes.md
│ │ │ │ ├── prerequisites/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ └── docker/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── advanced.md
│ │ │ │ │ ├── command.md
│ │ │ │ │ ├── docker.md
│ │ │ │ │ ├── images.md
│ │ │ │ │ ├── install.md
│ │ │ │ │ └── introduction.md
│ │ │ │ ├── setup-components/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── install-components-kf.md
│ │ │ │ │ ├── install-components-mlflow.md
│ │ │ │ │ ├── install-components-pg.md
│ │ │ │ │ └── install-components-seldon.md
│ │ │ │ └── setup-kubernetes/
│ │ │ │ ├── _category_.json
│ │ │ │ ├── install-kubernetes/
│ │ │ │ │ ├── _category_.json
│ │ │ │ │ ├── kubernetes-with-k3s.md
│ │ │ │ │ ├── kubernetes-with-kubeadm.md
│ │ │ │ │ └── kubernetes-with-minikube.md
│ │ │ │ ├── install-kubernetes-module.md
│ │ │ │ ├── install-prerequisite.md
│ │ │ │ ├── intro.md
│ │ │ │ ├── kubernetes.md
│ │ │ │ └── setup-nvidia-gpu.md
│ │ │ └── version-1.0.json
│ │ ├── docusaurus-plugin-content-docs-community/
│ │ │ ├── current/
│ │ │ │ └── community/
│ │ │ │ ├── community.md
│ │ │ │ ├── contributors.md
│ │ │ │ └── how-to-contribute.md
│ │ │ └── current.json
│ │ └── docusaurus-theme-classic/
│ │ ├── footer.json
│ │ └── navbar.json
│ └── ko/
│ ├── code.json
│ ├── docusaurus-plugin-content-blog/
│ │ └── options.json
│ ├── docusaurus-plugin-content-docs/
│ │ ├── current.json
│ │ └── version-1.0.json
│ ├── docusaurus-plugin-content-docs-community/
│ │ └── current.json
│ └── docusaurus-theme-classic/
│ ├── footer.json
│ └── navbar.json
├── package.json
├── python/
│ ├── env/
│ │ └── .gitkeep
│ ├── pyproject.toml
│ └── translation/
│ └── main.py
├── sidebars.js
├── sidebarsCommunity.js
├── src/
│ ├── components/
│ │ ├── HomepageFeatures/
│ │ │ ├── index.tsx
│ │ │ └── styles.module.css
│ │ └── TeamProfileCards/
│ │ └── index.tsx
│ ├── css/
│ │ └── custom.css
│ └── pages/
│ ├── index.module.css
│ ├── index.tsx
│ └── markdown-page.md
├── static/
│ ├── .nojekyll
│ ├── googlee5904fe980148e9b.html
│ └── img/
│ └── site.webmanifest
├── tsconfig.json
├── versioned_docs/
│ └── version-1.0/
│ ├── api-deployment/
│ │ ├── _category_.json
│ │ ├── seldon-children.md
│ │ ├── seldon-fields.md
│ │ ├── seldon-iris.md
│ │ ├── seldon-mlflow.md
│ │ ├── seldon-pg.md
│ │ └── what-is-api-deployment.md
│ ├── appendix/
│ │ ├── _category_.json
│ │ ├── metallb.md
│ │ └── pyenv.md
│ ├── further-readings/
│ │ ├── _category_.json
│ │ └── info.md
│ ├── introduction/
│ │ ├── _category_.json
│ │ ├── component.md
│ │ ├── intro.md
│ │ ├── levels.md
│ │ └── why_kubernetes.md
│ ├── kubeflow/
│ │ ├── _category_.json
│ │ ├── advanced-component.md
│ │ ├── advanced-environment.md
│ │ ├── advanced-mlflow.md
│ │ ├── advanced-pipeline.md
│ │ ├── advanced-run.md
│ │ ├── basic-component.md
│ │ ├── basic-pipeline-upload.md
│ │ ├── basic-pipeline.md
│ │ ├── basic-requirements.md
│ │ ├── basic-run.md
│ │ ├── how-to-debug.md
│ │ ├── kubeflow-concepts.md
│ │ └── kubeflow-intro.md
│ ├── kubeflow-dashboard-guide/
│ │ ├── _category_.json
│ │ ├── experiments-and-others.md
│ │ ├── experiments.md
│ │ ├── intro.md
│ │ ├── notebooks.md
│ │ ├── tensorboards.md
│ │ └── volumes.md
│ ├── prerequisites/
│ │ ├── _category_.json
│ │ └── docker/
│ │ ├── _category_.json
│ │ ├── advanced.md
│ │ ├── command.md
│ │ ├── docker.md
│ │ ├── images.md
│ │ ├── install.md
│ │ └── introduction.md
│ ├── setup-components/
│ │ ├── _category_.json
│ │ ├── install-components-kf.md
│ │ ├── install-components-mlflow.md
│ │ ├── install-components-pg.md
│ │ └── install-components-seldon.md
│ └── setup-kubernetes/
│ ├── _category_.json
│ ├── install-kubernetes/
│ │ ├── _category_.json
│ │ ├── kubernetes-with-k3s.md
│ │ ├── kubernetes-with-kubeadm.md
│ │ └── kubernetes-with-minikube.md
│ ├── install-kubernetes-module.md
│ ├── install-prerequisite.md
│ ├── intro.md
│ ├── kubernetes.md
│ └── setup-nvidia-gpu.md
├── versioned_sidebars/
│ └── version-1.0-sidebars.json
└── versions.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
* @mlops-for-all/maintainers
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Changes?
<!-- 이 pr 로 인해서 무엇이 변경되었는지 작성해주세요 -->
## Why we need?
<!-- 이 pr 이 왜 필요한지 작성해주세요 -->
## Test?
- [ ] `npm run start` 를 수행하여 로컬에서 렌더링된 페이지를 확인하셨나요?
- [ ] `npm test` 를 통과하였나요?
## Anything Else? (Optional)
<!-- 스크린샷, 환경 정보, 주의사항 등 필요한 추가정보가 있다면 작성해주세요. -->
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy to GitHub Pages
on:
push:
branches:
- main
# Review gh actions docs if you want to further define triggers, paths, etc
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
jobs:
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- name: Install dependencies
run: npm ci
- name: Build website
run: npm run build
# Popular action to deploy to GitHub Pages:
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.ORG_PAT }}
# Build output to publish to the `gh-pages` branch:
publish_dir: ./build
# The following lines assign commit authorship to the official
# GH-Actions bot for deploys to `gh-pages` branch:
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
# The GH actions bot is used by default if you didn't specify the two fields.
# You can swap them out with your own user credentials.
user_name: github-actions[bot]
user_email: 41898282+github-actions[bot]@users.noreply.github.com
================================================
FILE: .github/workflows/pull-request.yml
================================================
name: "Pull Request"
on:
pull_request:
types: [opened, synchronize, edited, reopened, closed]
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: anencore94/labeler@v1.1.0
================================================
FILE: .gitignore
================================================
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
v1/
.vscode
openai.env
.envrc
__pycache__
================================================
FILE: README.md
================================================
## 모두의 MLOps
모두의 MLOps 프로젝트입니다.
프로젝트에 누구던 자유롭게 기여할 수 있습니다.
자세한 내용은 [How to Contribute](https://mlops-for-all.github.io/community/how-to-contribute/)를 참조하세요.
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
================================================
FILE: community/community.md
================================================
---
title: "Community"
sidebar_position: 1
---
### *모두의 MLOps* 릴리즈 소식
새로운 포스트나 수정사항은 [Announcements](https://github.com/mlops-for-all/mlops-for-all.github.io/discussions/categories/announcements)에서 확인할 수 있습니다.
### Question
프로젝트 내용과 관련된 궁금점은 [Q&A](https://github.com/mlops-for-all/mlops-for-all.github.io/discussions/categories/q-a)를 통해 질문할 수 있습니다.
### Suggestion
제안점은 [Ideas](https://github.com/mlops-for-all/mlops-for-all.github.io/discussions/categories/ideas)를 통해 제안해 주시면 됩니다.
### Copyright
1. 본 문서를 “비상업적 목적” 사용 시 하기와 같이 출처를 반드시 표시해주세요.
- MLOps for ALL, https://mlops-for-all.github.io/
2. 상업적 용도로 인용/사용/차용하고자 하는 경우 마키나락스(contact@makinarocks.ai)로 사전에 문의주시기 바랍니다.
================================================
FILE: community/contributors.md
================================================
---
sidebar_position: 3
---
# Contributors
## Main Authors
import {
MainAuthorRow,
} from '@site/src/components/TeamProfileCards';
<MainAuthorRow />
## Contributors
Thank you for contributing our tutorials!
import {
ContributorsRow,
} from '@site/src/components/TeamProfileCards';
<ContributorsRow />
================================================
FILE: community/how-to-contribute.md
================================================
---
title: "How to Contribute"
sidebar_position: 2
---
## How to Start
### Git Repo 준비
1. [*모두의 MLOps* GitHub Repository](https://github.com/mlops-for-all/mlops-for-all.github.io)에 접속합니다.
2. 여러분의 개인 Repository로 `Fork`합니다.
3. Forked Repository를 여러분의 작업 환경으로 `git clone`합니다.
### 환경 설정
1. 모두의 MLOps는 Hugo 와 Node를 이용하고 있습니다.
다음 명령어를 통해 필요한 패키지가 설치되어 있는지 확인합니다.
- node & npm
```bash
npm --version
```
- hugo
```bash
hugo version
```
1. 필요한 node module을 설치합니다.
```bash
npm install
```
2. 프로젝트에서는 각 글의 일관성을 위해서 여러 markdown lint를 적용하고 있습니다.
다음 명령어를 실행해 test를 진행한 후 커밋합니다.내용 수정 및 추가 후 lint가 맞는지 확인합니다.
```bash
npm test
```
4. lint 확인 완료 후 ci 를 실행합니다.
```bash
npm ci
```
4. 로컬에서 실행 후 수정한 글이 정상적으로 나오는지 확인합니다.
```bash
npm run start
```
## How to Contribute
### 1. 새로운 포스트를 작성할 때
새로운 포스트는 각 챕터와 포스트의 위치에 맞는 weight를 설정합니다.
- Introduction: 1xx
- Setup: 2xx
- Kubeflow: 3xx
- API Deployment: 4xx
- Help: 10xx
### 2. 기존의 포스트를 수정할 때
기존의 포스트를 수정할 때 Contributor에 본인의 이름을 입력합니다.
```markdown
contributors: ["John Doe", "Adam Smith"]
```
### 3. 프로젝트에 처음 기여할 때
만약 프로젝트에 처음 기여 할 때 `content/kor/contributors`에 본인의 이름으로 폴더를 생성한 후, `_index.md`라는 파일을 작성합니다.
예를 들어, `minsoo kim`이 본인의 영어 이름이라면, 폴더명은 `minsoo-kim`으로 하여 해당 폴더 내부의 `_index.md`파일에 다음의 내용을 작성합니다.
폴더명은 하이픈(-)으로 연결한 소문자로, title은 띄어쓰기를 포함한 CamelCase로 작성합니다.
```markdown
---
title: "John Doe"
draft: false
---
```
## After Pull Request
Pull Request를 생성하면 프로젝트에서는 자동으로 *모두의 MLOps* 운영진에게 리뷰 요청이 전해집니다. 최대 일주일 이내로 확인 후 Comment를 드릴 예정입니다.
================================================
FILE: docs/api-deployment/_category_.json
================================================
{
"label": "API Deployment",
"position": 7,
"link": {
"type": "generated-index"
}
}
================================================
FILE: docs/api-deployment/seldon-children.md
================================================
---
title : "6. Multi Models"
description: ""
sidebar_position: 6
contributors: ["Jongseob Jeon"]
---
## Multi Models
앞서 설명했던 방법들은 모두 단일 모델을 대상으로 했습니다.
이번 페이지에서는 여러 개의 모델을 연결하는 방법에 대해서 알아봅니다.
## Pipeline
우선 모델을 2개를 생성하는 파이프라인을 작성하겠습니다.
모델은 앞서 사용한 SVC 모델에 StandardScaler를 추가하고 저장하도록 하겠습니다.
```python
from functools import partial
import kfp
from kfp.components import InputPath, OutputPath, create_component_from_func
@partial(
create_component_from_func,
packages_to_install=["pandas", "scikit-learn"],
)
def load_iris_data(
data_path: OutputPath("csv"),
target_path: OutputPath("csv"),
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow"],
)
def train_scaler_from_csv(
data_path: InputPath("csv"),
scaled_data_path: OutputPath("csv"),
model_path: OutputPath("dill"),
input_example_path: OutputPath("dill"),
signature_path: OutputPath("dill"),
conda_env_path: OutputPath("dill"),
):
import dill
import pandas as pd
from sklearn.preprocessing import StandardScaler
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
data = pd.read_csv(data_path)
scaler = StandardScaler()
scaled_data = scaler.fit_transform(data)
scaled_data = pd.DataFrame(scaled_data, columns=data.columns, index=data.index)
scaled_data.to_csv(scaled_data_path, index=False)
with open(model_path, mode="wb") as file_writer:
dill.dump(scaler, file_writer)
input_example = data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(data, scaler.transform(data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["scikit-learn"],
install_mlflow=False
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow"],
)
def train_svc_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
input_example_path: OutputPath("dill"),
signature_path: OutputPath("dill"),
conda_env_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
input_example = train_data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(train_data, clf.predict(train_data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["scikit-learn"],
install_mlflow=False
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow", "boto3"],
)
def upload_sklearn_model_to_mlflow(
model_name: str,
model_path: InputPath("dill"),
input_example_path: InputPath("dill"),
signature_path: InputPath("dill"),
conda_env_path: InputPath("dill"),
):
import os
import dill
from mlflow.sklearn import save_model
from mlflow.tracking.client import MlflowClient
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://minio-service.kubeflow.svc:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minio"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123"
client = MlflowClient("http://mlflow-server-service.mlflow-system.svc:5000")
with open(model_path, mode="rb") as file_reader:
clf = dill.load(file_reader)
with open(input_example_path, "rb") as file_reader:
input_example = dill.load(file_reader)
with open(signature_path, "rb") as file_reader:
signature = dill.load(file_reader)
with open(conda_env_path, "rb") as file_reader:
conda_env = dill.load(file_reader)
save_model(
sk_model=clf,
path=model_name,
serialization_format="cloudpickle",
conda_env=conda_env,
signature=signature,
input_example=input_example,
)
run = client.create_run(experiment_id="0")
client.log_artifact(run.info.run_id, model_name)
from kfp.dsl import pipeline
@pipeline(name="multi_model_pipeline")
def multi_model_pipeline(kernel: str = "rbf"):
iris_data = load_iris_data()
scaled_data = train_scaler_from_csv(data=iris_data.outputs["data"])
_ = upload_sklearn_model_to_mlflow(
model_name="scaler",
model=scaled_data.outputs["model"],
input_example=scaled_data.outputs["input_example"],
signature=scaled_data.outputs["signature"],
conda_env=scaled_data.outputs["conda_env"],
)
model = train_svc_from_csv(
train_data=scaled_data.outputs["scaled_data"],
train_target=iris_data.outputs["target"],
kernel=kernel,
)
_ = upload_sklearn_model_to_mlflow(
model_name="svc",
model=model.outputs["model"],
input_example=model.outputs["input_example"],
signature=model.outputs["signature"],
conda_env=model.outputs["conda_env"],
)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(multi_model_pipeline, "multi_model_pipeline.yaml")
```
파이프라인을 업로드하면 다음과 같이 나옵니다.

MLflow 대시보드를 확인하면 다음과 같이 두 개의 모델이 생성됩니다.

각각의 run_id를 확인 후 다음과 같이 SeldonDeployment 스펙을 정의합니다.
```bash
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: multi-model-example
namespace: kubeflow-user-example-com
spec:
name: model
predictors:
- name: model
componentSpecs:
- spec:
volumes:
- name: model-provision-location
emptyDir: {}
initContainers:
- name: scaler-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/7f445015a0e94519b003d316478766ef/artifacts/scaler"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
- name: svc-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/87eb168e76264b39a24b0e5ca0fe922b/artifacts/svc"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
containers:
- name: scaler
image: seldonio/mlflowserver:1.8.0-dev
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
- name: svc
image: seldonio/mlflowserver:1.8.0-dev
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
graph:
name: scaler
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
- name: predict_method
type: STRING
value: "transform"
children:
- name: svc
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
```
모델이 두 개가 되었으므로 각 모델의 initContainer와 container를 정의해주어야 합니다.
이 필드는 입력값을 array로 받으며 순서는 관계없습니다.
모델이 실행하는 순서는 graph에서 정의됩니다.
```bash
graph:
name: scaler
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
- name: predict_method
type: STRING
value: "transform"
children:
- name: svc
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
```
graph의 동작 방식은 처음 받은 값을 정해진 predict_method로 변환한 뒤 children으로 정의된 모델에 전달하는 방식입니다.
이 경우 scaler -> svc 로 데이터가 전달됩니다.
이제 위의 스펙을 yaml파일로 생성해 보겠습니다.
```bash
cat <<EOF > multi-model.yaml
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: multi-model-example
namespace: kubeflow-user-example-com
spec:
name: model
predictors:
- name: model
componentSpecs:
- spec:
volumes:
- name: model-provision-location
emptyDir: {}
initContainers:
- name: scaler-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/7f445015a0e94519b003d316478766ef/artifacts/scaler"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
- name: svc-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/87eb168e76264b39a24b0e5ca0fe922b/artifacts/svc"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
containers:
- name: scaler
image: ghcr.io/mlops-for-all/mlflowserver
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
- name: svc
image: ghcr.io/mlops-for-all/mlflowserver
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
graph:
name: scaler
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
- name: predict_method
type: STRING
value: "transform"
children:
- name: svc
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
EOF
```
다음 명령어를 통해 API를 생성합니다.
```bash
kubectl apply -f multi-model.yaml
```
정상적으로 수행되면 다음과 같이 출력됩니다.
```bash
seldondeployment.machinelearning.seldon.io/multi-model-example created
```
정상적으로 생성됐는지 확인합니다.
```bash
kubectl get po -n kubeflow-user-example-com | grep multi-model-example
```
정상적으로 생성되면 다음과 비슷한 pod이 생성됩니다.
```bash
multi-model-example-model-0-scaler-svc-9955fb795-n9ffw 4/4 Running 0 2m30s
```
================================================
FILE: docs/api-deployment/seldon-fields.md
================================================
---
title : "4. Seldon Fields"
description: ""
sidebar_position: 4
contributors: ["Jongseob Jeon"]
---
## How Seldon Core works?
Seldon Core가 API 서버를 생성하는 과정을 요약하면 다음과 같습니다.

1. initContainer는 모델 저장소에서 필요한 모델을 다운로드 받습니다.
2. 다운로드받은 모델을 container로 전달합니다.
3. container는 전달받은 모델을 감싼 API 서버를 실행합니다.
4. 생성된 API 서버 주소로 API를 요청하여 모델의 추론 값을 받을 수 있습니다.
## SeldonDeployment Spec
Seldon Core를 사용할 때, 주로 사용하게 되는 커스텀 리소스인 SeldonDeployment를 정의하는 yaml 파일은 다음과 같습니다.
```bash
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: seldon-example
namespace: kubeflow-user-example-com
spec:
name: model
predictors:
- name: model
componentSpecs:
- spec:
volumes:
- name: model-provision-location
emptyDir: {}
initContainers:
- name: model-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "gs://seldon-models/v1.12.0-dev/sklearn/iris"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
containers:
- name: model
image: seldonio/sklearnserver:1.8.0-dev
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
graph:
name: model
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
children: []
```
SeldonDeployment spe 중 `name` 과 `predictors` 필드는 required 필드입니다.
`name`은 쿠버네티스 상에서 pod의 구분을 위한 이름으로 크게 영향을 미치지 않습니다.
`predictors`는 한 개로 구성된 array로 `name`, `componentSpecs` 와 `graph` 가 정의되어야 합니다.
여기서도 `name`은 pod의 구분을 위한 이름으로 크게 영향을 미치지 않습니다.
이제 `componentSpecs` 와 `graph`에서 정의해야 할 필드들에 대해서 알아보겠습니다.
## componentSpecs
`componentSpecs` 는 하나로 구성된 array로 `spec` 키값이 정의되어야 합니다.
`spec` 에는 `volumes`, `initContainers`, `containers` 의 필드가 정의되어야 합니다.
### volumes
```bash
volumes:
- name: model-provision-location
emptyDir: {}
```
`volumes`은 initContainer에서 다운로드받는 모델을 저장하기 위한 공간을 의미합니다.
array로 입력을 받으며 array의 구성 요소는 `name`과 `emptyDir` 입니다.
이 값들은 모델을 다운로드받고 옮길 때 한번 사용되므로 크게 수정하지 않아도 됩니다.
### initContainer
```bash
- name: model-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "gs://seldon-models/v1.12.0-dev/sklearn/iris"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
```
initContainer는 API에서 사용할 모델을 다운로드받는 역할을 합니다.
그래서 사용되는 필드들은 모델 저장소(Model Registry)로부터 데이터를 다운로드받을 때 필요한 정보들을 정해줍니다.
initContainer의 값은 n개의 array로 구성되어 있으며 사용하는 모델마다 각각 지정해주어야 합니다.
#### name
`name`은 쿠버네티스 상의 pod의 이름입니다.
디버깅을 위해 `{model_name}-initializer` 로 사용하길 권장합니다.
#### image
`image` 는 모델을 다운로드 받기 위해 사용할 이미지 이름입니다.
seldon core에서 권장하는 이미지는 크게 두 가지입니다.
- gcr.io/kfserving/storage-initializer:v0.4.0
- seldonio/rclone-storage-initializer:1.13.0-dev
각각의 자세한 내용은 다음을 참고 바랍니다.
- [kfserving](https://docs.seldon.io/projects/seldon-core/en/latest/servers/kfserving-storage-initializer.html)
- [rclone](https://github.com/SeldonIO/seldon-core/tree/master/components/rclone-storage-initializer)
*모두의 MLOps* 에서는 kfserving을 사용합니다.
#### args
```bash
args:
- "gs://seldon-models/v1.12.0-dev/sklearn/iris"
- "/mnt/models"
```
gcr.io/kfserving/storage-initializer:v0.4.0 도커 이미지가 실행(`run`)될 때 입력받는 argument를 입력합니다.
array로 구성되며 첫 번째 array의 값은 다운로드받을 모델의 주소를 적습니다.
두 번째 array의 값은 다운로드받은 모델을 저장할 주소를 적습니다. (seldon core에서는 주로 `/mnt/models`에 저장합니다.)
### volumeMounts
```bash
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
```
`volumneMounts`는 volumes에서 설명한 것과 같이 `/mnt/models`를 쿠버네티스 상에서 공유할 수 있도록 볼륨을 붙여주는 필드입니다.
자세한 내용은 [쿠버네티스 Volume](https://kubernetes.io/docs/concepts/storage/volumes/)을 참조 바랍니다.
### container
```bash
containers:
- name: model
image: seldonio/sklearnserver:1.8.0-dev
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
```
container는 실제로 모델이 API 형식으로 실행될 때의 설정을 정의하는 필드입니다.
#### name
`name`은 쿠버네티스 상의 pod의 이름입니다. 사용하는 모델의 이름을 적습니다.
#### image
`image` 는 모델을 API로 만드는 데 사용할 이미지입니다.
이미지에는 모델이 로드될 때 필요한 패키지들이 모두 설치되어 있어야 합니다.
Seldon Core에서 지원하는 공식 이미지는 다음과 같습니다.
- seldonio/sklearnserver
- seldonio/mlflowserver
- seldonio/xgboostserver
- seldonio/tfserving
#### volumeMounts
```bash
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
```
initContainer에서 다운로드받은 데이터가 있는 경로를 알려주는 필드입니다.
이때 모델이 수정되는 것을 방지하기 위해 `readOnly: true`도 같이 주겠습니다.
#### securityContext
```bash
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
```
필요한 패키지를 설치할 때 pod이 권한이 없어서 패키지 설치를 수행하지 못할 수 있습니다.
이를 위해서 root 권한을 부여합니다. (다만 이 작업은 실제 서빙 시 보안 문제가 생길 수 있습니다.)
## graph
```bash
graph:
name: model
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
children: []
```
모델이 동작하는 순서를 정의한 필드입니다.
### name
모델 그래프의 이름입니다. container에서 정의된 이름을 사용합니다.
### type
type은 크게 4가지가 있습니다.
1. TRANSFORMER
2. MODEL
3. OUTPUT_TRANSFORMER
4. ROUTER
각 type에 대한 자세한 설명은 [Seldon Core Complex Graphs Metadata Example](https://docs.seldon.io/projects/seldon-core/en/latest/examples/graph-metadata.html)을 참조 바랍니다.
### parameters
class init 에서 사용되는 값들입니다.
sklearnserver에서 필요한 값은 [다음 파일](https://github.com/SeldonIO/seldon-core/blob/master/servers/sklearnserver/sklearnserver/SKLearnServer.py)에서 확인할 수 있습니다.
```python
class SKLearnServer(SeldonComponent):
def __init__(self, model_uri: str = None, method: str = "predict_proba"):
```
코드를 보면 `model_uri`와 `method`를 정의할 수 있습니다.
### children
순서도를 작성할 때 사용됩니다. 자세한 내용은 다음 페이지에서 설명합니다.
================================================
FILE: docs/api-deployment/seldon-iris.md
================================================
---
title : "2. Deploy SeldonDeployment"
description: ""
sidebar_position: 2
date: 2021-12-22
lastmod: 2021-12-22
contributors: ["Youngcheol Jang", "SeungTae Kim"]
---
## SeldonDeployment를 통해 배포하기
이번에는 학습된 모델이 있을 때 SeldonDeployment를 통해 API Deployment를 해보겠습니다.
SeldonDeployment는 쿠버네티스(Kubernetes)에 모델을 REST/gRPC 서버의 형태로 배포하기 위해 정의된 CRD(CustomResourceDefinition)입니다.
### 1. Prerequisites
SeldonDeployment 관련된 실습은 seldon-deploy라는 새로운 네임스페이스(namespace)에서 진행하도록 하겠습니다.
네임스페이스를 생성한 뒤, seldon-deploy를 현재 네임스페이스로 설정합니다.
```bash
kubectl create namespace seldon-deploy
kubectl config set-context --current --namespace=seldon-deploy
```
### 2. 스펙 정의
SeldonDeployment를 배포하기 위한 yaml 파일을 생성합니다.
이번 페이지에서는 공개된 iris model을 사용하도록 하겠습니다.
이 iris model은 sklearn 프레임워크를 통해 학습되었기 때문에 SKLEARN_SERVER를 사용합니다.
```bash
cat <<EOF > iris-sdep.yaml
apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
name: sklearn
namespace: seldon-deploy
spec:
name: iris
predictors:
- graph:
children: []
implementation: SKLEARN_SERVER
modelUri: gs://seldon-models/v1.12.0-dev/sklearn/iris
name: classifier
name: default
replicas: 1
EOF
```
yaml 파일을 배포합니다.
```bash
kubectl apply -f iris-sdep.yaml
```
다음 명령어를 통해 정상적으로 배포가 되었는지 확인합니다.
```bash
kubectl get pods --selector seldon-app=sklearn-default -n seldon-deploy
```
모두 Running 이 되면 다음과 비슷한 결과가 출력됩니다.
```bash
NAME READY STATUS RESTARTS AGE
sklearn-default-0-classifier-5fdfd7bb77-ls9tr 2/2 Running 0 5m
```
## Ingress URL
이제 배포된 모델에 추론 요청(predict request)를 보내서 추론 결괏값을 받아옵니다.
배포된 API는 다음과 같은 규칙으로 생성됩니다.
`http://{NODE_IP}:{NODE_PORT}/seldon/{namespace}/{seldon-deployment-name}/api/v1.0/{method-name}/`
### NODE_IP / NODE_PORT
[Seldon Core 설치 시, Ambassador를 Ingress Controller로 설정하였으므로](../setup-components/install-components-seldon.md), SeldonDeployment로 생성된 API 서버는 모두 Ambassador의 Ingress gateway를 통해 요청할 수 있습니다.
따라서 우선 Ambassador Ingress Gateway의 url을 환경 변수로 설정합니다.
```bash
export NODE_IP=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')
export NODE_PORT=$(kubectl get service ambassador -n seldon-system -o jsonpath="{.spec.ports[0].nodePort}")
```
설정된 url을 확인합니다.
```bash
echo "NODE_IP"=$NODE_IP
echo "NODE_PORT"=$NODE_PORT
```
다음과 비슷하게 출력되어야 하며, 클라우드 등을 통해 설정할 경우, internal ip 주소가 설정되는 것을 확인할 수 있습니다.
```bash
NODE_IP=192.168.0.19
NODE_PORT=30486
```
### namespace / seldon-deployment-name
SeldonDeployment가 배포된 `namespace`와 `seldon-deployment-name`를 의미합니다.
이는 스펙을 정의할 때 metadata에 정의된 값을 사용합니다.
```bash
metadata:
name: sklearn
namespace: seldon-deploy
```
위의 예시에서는 `namespace`는 seldon-deploy, `seldon-deployment-name`은 sklearn 입니다.
### method-name
SeldonDeployment에서 주로 사용하는 `method-name`은 두 가지가 있습니다.
1. doc
2. predictions
각각의 method의 자세한 사용 방법은 아래에서 설명합니다.
## Using Swagger
우선 doc method를 사용하는 방법입니다. doc method를 이용하면 seldon에서 생성한 swagger에 접속할 수 있습니다.
### 1. Swagger 접속
위에서 설명한 ingress url 규칙에 따라 아래 주소를 통해 swagger에 접근할 수 있습니다.
`http://192.168.0.19:30486/seldon/seldon-deploy/sklearn/api/v1.0/doc/`

### 2. Swagger Predictions 메뉴 선택
UI에서 `/seldon/seldon-deploy/sklearn/api/v1.0/predictions` 메뉴를 선택합니다.

### 3. *Try it out* 선택

### 4. Request body에 data 입력

다음 데이터를 입력합니다.
```bash
{
"data": {
"ndarray":[[1.0, 2.0, 5.0, 6.0]]
}
}
```
### 5. 추론 결과 확인
`Execute` 버튼을 눌러서 추론 결과를 확인할 수 있습니다.

정상적으로 수행되면 다음과 같은 추론 결과를 얻습니다.
```bash
{
"data": {
"names": [
"t:0",
"t:1",
"t:2"
],
"ndarray": [
[
9.912315378486697e-7,
0.0007015931307746079,
0.9992974156376876
]
]
},
"meta": {
"requestPath": {
"classifier": "seldonio/sklearnserver:1.11.2"
}
}
}
```
## Using CLI
또한, curl과 같은 http client CLI 도구를 활용해서도 API 요청을 수행할 수 있습니다.
예를 들어, 다음과 같이 `/predictions`를 요청하면
```bash
curl -X POST http://$NODE_IP:$NODE_PORT/seldon/seldon-deploy/sklearn/api/v1.0/predictions \
-H 'Content-Type: application/json' \
-d '{ "data": { "ndarray": [[1,2,3,4]] } }'
```
아래와 같은 응답이 정상적으로 출력되는 것을 확인할 수 있습니다.
```bash
{"data":{"names":["t:0","t:1","t:2"],"ndarray":[[0.0006985194531162835,0.00366803903943666,0.995633441507447]]},"meta":{"requestPath":{"classifier":"seldonio/sklearnserver:1.11.2"}}}
```
================================================
FILE: docs/api-deployment/seldon-mlflow.md
================================================
---
title : "5. Model from MLflow"
description: ""
sidebar_position: 5
contributors: ["Jongseob Jeon"]
---
## Model from MLflow
이번 페이지에서는 [MLflow Component](../kubeflow/advanced-mlflow.md)에서 저장된 모델을 이용해 API를 생성하는 방법에 대해서 알아보겠습니다.
## Secret
initContainer가 minio에 접근해서 모델을 다운로드받으려면 credentials가 필요합니다.
minio에 접근하기 위한 credentials는 다음과 같습니다.
```bash
apiVersion: v1
type: Opaque
kind: Secret
metadata:
name: seldon-init-container-secret
namespace: kubeflow-user-example-com
data:
AWS_ACCESS_KEY_ID: bWluaW8K=
AWS_SECRET_ACCESS_KEY: bWluaW8xMjM=
AWS_ENDPOINT_URL: aHR0cDovL21pbmlvLm1ha2luYXJvY2tzLmFp
USE_SSL: ZmFsc2U=
```
`AWS_ACCESS_KEY_ID` 의 입력값은 `minio`입니다. 다만 secret의 입력값은 인코딩된 값이여야 되기 때문에 실제로 입력되는 값은 다음을 수행후 나오는 값이어야 합니다.
data에 입력되어야 하는 값들은 다음과 같습니다.
- AWS_ACCESS_KEY_ID: minio
- AWS_SECRET_ACCESS_KEY: minio123
- AWS_ENDPOINT_URL: http://minio-service.kubeflow.svc:9000
- USE_SSL: false
인코딩은 다음 명령어를 통해서 할 수 있습니다.
```bash
echo -n minio | base64
```
그러면 다음과 같은 값이 출력됩니다.
```bash
bWluaW8=
```
인코딩을 전체 값에 대해서 진행하면 다음과 같이 됩니다.
- AWS_ACCESS_KEY_ID: bWluaW8=
- AWS_SECRET_ACCESS_KEY: bWluaW8xMjM=
- AWS_ENDPOINT_URL: aHR0cDovL21pbmlvLXNlcnZpY2Uua3ViZWZsb3cuc3ZjOjkwMDA=
- USE_SSL: ZmFsc2U=
다음 명령어를 통해 secret을 생성할 수 있는 yaml파일을 생성합니다.
```bash
cat <<EOF > seldon-init-container-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: seldon-init-container-secret
namespace: kubeflow-user-example-com
type: Opaque
data:
AWS_ACCESS_KEY_ID: bWluaW8=
AWS_SECRET_ACCESS_KEY: bWluaW8xMjM=
AWS_ENDPOINT_URL: aHR0cDovL21pbmlvLXNlcnZpY2Uua3ViZWZsb3cuc3ZjOjkwMDA=
USE_SSL: ZmFsc2U=
EOF
```
다음 명령어를 통해 secret을 생성합니다.
```bash
kubectl apply -f seldon-init-container-secret.yaml
```
정상적으로 수행되면 다음과 같이 출력됩니다.
```bash
secret/seldon-init-container-secret created
```
## Seldon Core yaml
이제 Seldon Core를 생성하는 yaml파일을 작성합니다.
```bash
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: seldon-example
namespace: kubeflow-user-example-com
spec:
name: model
predictors:
- name: model
componentSpecs:
- spec:
volumes:
- name: model-provision-location
emptyDir: {}
initContainers:
- name: model-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/74ba8e33994144f599e50b3be176cdb0/artifacts/svc"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
containers:
- name: model
image: ghcr.io/mlops-for-all/mlflowserver
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
graph:
name: model
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
children: []
```
이 전에 작성한 [Seldon Fields](../api-deployment/seldon-fields.md)와 달라진 점은 크게 두 부분입니다.
initContainer에 `envFrom` 필드가 추가되었으며 args의 주소가 `s3://mlflow/mlflow/artifacts/0/74ba8e33994144f599e50b3be176cdb0/artifacts/svc` 로 바뀌었습니다.
### args
앞서 args의 첫번째 array는 우리가 다운로드받을 모델의 경로라고 했습니다.
그럼 mlflow에 저장된 모델의 경로는 어떻게 알 수 있을까요?
다시 mlflow에 들어가서 run을 클릭하고 모델을 누르면 다음과 같이 확인할 수 있습니다.

이렇게 확인된 경로를 입력하면 됩니다.
### envFrom
minio에 접근해서 모델을 다운로드 받는 데 필요한 환경변수를 입력해주는 과정입니다.
앞서 만든 `seldon-init-container-secret`를 이용합니다.
## API 생성
우선 위에서 정의한 스펙을 yaml 파일로 생성하겠습니다.
```bash
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: seldon-example
namespace: kubeflow-user-example-com
spec:
name: model
predictors:
- name: model
componentSpecs:
- spec:
volumes:
- name: model-provision-location
emptyDir: {}
initContainers:
- name: model-initializer
image: gcr.io/kfserving/storage-initializer:v0.4.0
args:
- "s3://mlflow/mlflow/artifacts/0/74ba8e33994144f599e50b3be176cdb0/artifacts/svc"
- "/mnt/models"
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
envFrom:
- secretRef:
name: seldon-init-container-secret
containers:
- name: model
image: ghcr.io/mlops-for-all/mlflowserver
volumeMounts:
- mountPath: /mnt/models
name: model-provision-location
readOnly: true
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
graph:
name: model
type: MODEL
parameters:
- name: model_uri
type: STRING
value: "/mnt/models"
- name: xtype
type: STRING
value: "dataframe"
children: []
EOF
```
seldon pod을 생성합니다.
```bash
kubectl apply -f seldon-mlflow.yaml
```
정상적으로 수행되면 다음과 같이 출력됩니다.
```bash
seldondeployment.machinelearning.seldon.io/seldon-example created
```
이제 pod이 정상적으로 뜰 때까지 기다립니다.
```bash
kubectl get po -n kubeflow-user-example-com | grep seldon
```
다음과 비슷하게 출력되면 정상적으로 API를 생성했습니다.
```bash
seldon-example-model-0-model-5c949bd894-c5f28 3/3 Running 0 69s
```
CLI를 이용해 생성된 API에는 다음 request를 통해 실행을 확인할 수 있습니다.
```bash
curl -X POST http://$NODE_IP:$NODE_PORT/seldon/seldon-deploy/sklearn/api/v1.0/predictions \
-H 'Content-Type: application/json' \
-d '{
"data": {
"ndarray": [
[
143.0,
0.0,
30.0,
30.0
]
],
"names": [
"sepal length (cm)",
"sepal width (cm)",
"petal length (cm)",
"petal width (cm)"
]
}
}'
```
정상적으로 실행될 경우 다음과 같은 결과를 받을 수 있습니다.
```bash
{"data":{"names":[],"ndarray":["Virginica"]},"meta":{"requestPath":{"model":"ghcr.io/mlops-for-all/mlflowserver:e141f57"}}}
```
================================================
FILE: docs/api-deployment/seldon-pg.md
================================================
---
title : "3. Seldon Monitoring"
description: "Prometheus & Grafana 확인하기"
sidebar_position: 3
date: 2021-12-24
lastmod: 2021-12-24
contributors: ["Jongseob Jeon"]
---
## Grafana & Prometheus
이제, [지난 페이지](../api-deployment/seldon-iris.md)에서 생성했던 SeldonDeployment 로 API Request 를 반복적으로 수행해보고, 대시보드에 변화가 일어나는지 확인해봅니다.
### 대시보드
[앞서 생성한 대시보드](../setup-components/install-components-pg.md)를 포트 포워딩합니다.
```bash
kubectl port-forward svc/seldon-core-analytics-grafana -n seldon-system 8090:80
```
### API 요청
[앞서 생성한 Seldon Deployment](../api-deployment/seldon-iris.md#using-cli)에 요청을 **반복해서** 보냅니다.
```bash
curl -X POST http://$NODE_IP:$NODE_PORT/seldon/seldon-deploy/sklearn/api/v1.0/predictions \
-H 'Content-Type: application/json' \
-d '{ "data": { "ndarray": [[1,2,3,4]] } }'
```
그리고 그라파나 대시보드를 확인하면 다음과 같이 Global Request Rate 이 `0 ops` 에서 순간적으로 상승하는 것을 확인할 수 있습니다.

이렇게 프로메테우스와 그라파나가 정상적으로 설치된 것을 확인할 수 있습니다.
================================================
FILE: docs/api-deployment/what-is-api-deployment.md
================================================
---
title : "1. What is API Deployment?"
description: ""
sidebar_position: 1
date: 2021-12-22
lastmod: 2021-12-22
contributors: ["Youngcheol Jang"]
---
## API Deployment란?
머신러닝 모델을 학습한 뒤에는 어떻게 사용해야 할까요?
머신러닝을 학습할 때는 더 높은 성능의 모델이 나오기를 기대하지만, 학습된 모델을 사용하여 추론을 할 때는 빠르고 쉽게 추론 결과를 받아보고 싶을 것입니다.
모델의 추론 결과를 확인하고자 할 때 주피터 노트북이나 파이썬 스크립트를 통해 학습된 모델을 로드한 뒤 추론할 수 있습니다.
그렇지만 이런 방법은 모델이 클수록 모델을 불러오는 데 많은 시간을 소요하게 되어서 비효율적입니다. 또한 이렇게 이용하면 많은 사람이 모델을 이용할 수 없고 학습된 모델이 있는 환경에서밖에 사용할 수 없습니다.
그래서 실제 서비스에서 머신러닝이 사용될 때는 API를 이용해서 학습된 모델을 사용합니다. 모델은 API 서버가 구동되는 환경에서 한 번만 로드가 되며, DNS를 활용하여 외부에서도 쉽게 추론 결과를 받을 수 있고 다른 서비스와 연동할 수 있습니다.
하지만 모델을 API로 만드는 작업에는 생각보다 많은 부수적인 작업이 필요합니다.
그래서 API로 만드는 작업을 더 쉽게 하기 위해서 Tensorflow와 같은 머신러닝 프레임워크 진영에서는 추론 엔진(Inference engine)을 개발하였습니다.
추론 엔진들을 이용하면 해당 머신러닝 프레임워크로 개발되고 학습된 모델을 불러와 추론이 가능한 API(REST 또는 gRPC)를 생성합니다.
이러한 추론 엔진을 활용하여 구축한 API 서버로 추론하고자 하는 데이터를 담아 요청을 보내면, 추론 엔진이 추론 결과를 응답에 담아 전송하는 것입니다.
대표적으로 다음과 같은 오픈소스 추론 엔진들이 개발되었습니다.
- [Tensorflow : Tensorflow Serving](https://github.com/tensorflow/serving)
- [PyTorch : Torchserve](https://github.com/pytorch/serve)
- [Onnx : Onnx Runtime](https://github.com/microsoft/onnxruntime)
오프소스에서 공식적으로 지원하지는 않지만, 많이 쓰이는 sklearn, xgboost 프레임워크를 위한 추론 엔진도 개발되어 있습니다.
이처럼 모델의 추론 결과를 API의 형태로 받아볼 수 있도록 배포하는 것을 **API Deployment**라고 합니다.
## Serving Framework
위에서 다양한 추론 엔진들이 개발되었다는 사실을 소개해 드렸습니다.
쿠버네티스 환경에서 이러한 추론 엔진들을 사용하여 API Deployment를 한다면 어떤 작업이 필요할까요?
추론 엔진을 배포하기 위한 Deployment, 추론 요청을 보낼 Endpoint를 생성하기 위한 Service,
외부에서의 추론 요청을 추론 엔진으로 보내기 위한 Ingress 등 많은 쿠버네티스 리소스를 배포해 주어야 합니다.
이것 이외에도, 많은 추론 요청이 들어왔을 경우의 스케일 아웃(scale-out), 추론 엔진 상태에 대한 모니터링, 개선된 모델이 나왔을 경우 버전 업데이트 등 추론 엔진을 운영할 때의 요구사항은 한두 가지가 아닙니다.
이러한 많은 요구사항을 처리하기 위해 추론 엔진들을 쿠버네티스 환경 위에서 한 번 더 추상화한 **Serving Framework**들이 개발되었습니다.
개발된 Serving Framework들은 다음과 같은 오픈소스들이 있습니다.
- [Seldon Core](https://github.com/SeldonIO/seldon-core)
- [Kserve](https://github.com/kserve)
- [BentoML](https://github.com/bentoml/BentoML)
*모두의 MLOps*에서는 Seldon Core를 사용하여 API Deployment를 하는 과정을 다루어 보도록 하겠습니다.
================================================
FILE: docs/appendix/_category_.json
================================================
{
"label": "Appendix",
"position": 9,
"link": {
"type": "generated-index"
}
}
================================================
FILE: docs/appendix/metallb.md
================================================
---
title: "2. Bare Metal 클러스터용 load balancer metallb 설치"
sidebar_position: 2
---
## MetalLB란?
Kubernetes 사용 시 AWS, GCP, Azure 와 같은 클라우드 플랫폼에서는 자체적으로 로드 벨런서(Load Balancer)를 제공해 주지만, 온프레미스 클러스터에서는 로드 벨런싱 기능을 제공하는 모듈을 추가적으로 설치해야 합니다.
[MetalLB](https://metallb.universe.tf/)는 베어메탈 환경에서 사용할 수 있는 로드 벨런서를 제공하는 오픈소스 프로젝트 입니다.
## 요구사항
| 요구 사항 | 버전 및 내용 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| Kubernetes | 로드 벨런싱 기능이 없는 >= v1.13.0 |
| [호환가능한 네트워크 CNI](https://metallb.universe.tf/installation/network-addons/) | Calico, Canal, Cilium, Flannel, Kube-ovn, Kube-router, Weave Net |
| IPv4 주소 | MetalLB 배포에 사용 |
| BGP 모드를 사용할 경우 | BGP 기능을 지원하는 하나 이상의 라우터 |
| 노드 간 포트 TCP/UDP 7946 오픈 | memberlist 요구 사항
## MetalLB 설치
### Preparation
IPVS 모드에서 kube-proxy를 사용하는 경우 Kubernetes v1.14.2 이후부터는 엄격한 ARP(strictARP) 모드를 사용하도록 설정해야 합니다.
Kube-router는 기본적으로 엄격한 ARP를 활성화하므로 서비스 프록시로 사용할 경우에는 이 기능이 필요하지 않습니다.
엄격한 ARP 모드를 적용하기에 앞서, 현재 모드를 확인합니다.
```bash
# see what changes would be made, returns nonzero returncode if different
kubectl get configmap kube-proxy -n kube-system -o yaml | \
grep strictARP
```
```bash
strictARP: false
```
strictARP: false 가 출력되는 경우 다음을 실행하여 strictARP: true로 변경합니다.
(strictARP: true가 이미 출력된다면 다음 커맨드를 수행하지 않으셔도 됩니다.)
```bash
# actually apply the changes, returns nonzero returncode on errors only
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system
```
정상적으로 수행되면 다음과 같이 출력됩니다.
```bash
Warning: resource configmaps/kube-proxy is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
configmap/kube-proxy configured
```
### 설치 - Manifest
#### 1. MetalLB 를 설치합니다.
```bash
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/metallb.yaml
```
#### 2. 정상 설치 확인
metallb-system namespace 의 2 개의 pod 이 모두 Running 이 될 때까지 기다립니다.
```bash
kubectl get pod -n metallb-system
```
모두 Running 이 되면 다음과 비슷한 결과가 출력됩니다.
```bash
NAME READY STATUS RESTARTS AGE
controller-7dcc8764f4-8n92q 1/1 Running 1 1m
speaker-fnf8l 1/1 Running 1 1m
```
매니페스트의 구성 요소는 다음과 같습니다.
- metallb-system/controller
- deployment 로 배포되며, 로드 벨런싱을 수행할 external IP 주소의 할당을 처리하는 역할을 담당합니다.
- metallb-system/speaker
- daemonset 형태로 배포되며, 외부 트래픽과 서비스를 연결해 네트워크 통신이 가능하도록 구성하는 역할을 담당합니다.
서비스에는 컨트롤러 및 스피커와 구성 요소가 작동하는 데 필요한 RBAC 사용 권한이 포함됩니다.
## Configuration
MetalLB 의 로드 벨런싱 정책 설정은 관련 설정 정보를 담은 configmap 을 배포하여 설정할 수 있습니다.
MetalLB 에서 구성할 수 있는 모드로는 다음과 같이 2가지가 있습니다.
1. [Layer 2 모드](https://metallb.universe.tf/concepts/layer2/)
2. [BGP 모드](https://metallb.universe.tf/concepts/bgp/)
여기에서는 Layer 2 모드로 진행하겠습니다.
### Layer 2 Configuration
Layer 2 모드는 간단하게 사용할 IP 주소의 대역만 설정하면 됩니다.
Layer 2 모드를 사용할 경우 워커 노드의 네트워크 인터페이스에 IP를 바인딩 하지 않아도 되는데 로컬 네트워크의 ARP 요청에 직접 응답하여 컴퓨터의 MAC주소를 클라이언트에 제공하는 방식으로 작동하기 때문입니다.
다음 `metallb_config.yaml` 파일은 MetalLB 가 192.168.35.100 ~ 192.168.35.110의 IP에 대한 제어 권한을 제공하고 Layer 2 모드를 구성하는 설정입니다.
클러스터 노드와 클라이언트 노드가 분리된 경우, 192.168.35.100 ~ 192.168.35.110 대역이 클라이언트 노드와 클러스터 노드 모두 접근 가능한 대역이어야 합니다.
#### metallb_config.yaml
```bash
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.35.100-192.168.35.110 # IP 대역폭
```
위의 설정을 적용합니다.
```test
kubectl apply -f metallb_config.yaml
```
정상적으로 배포하면 다음과 같이 출력됩니다.
```test
configmap/config created
```
## MetalLB 사용
### Kubeflow Dashboard
먼저 kubeflow의 Dashboard 를 제공하는 istio-system 네임스페이스의 istio-ingressgateway 서비스의 타입을 `LoadBalancer`로 변경하여 MetalLB로부터 로드 벨런싱 기능을 제공받기 전에, 현재 상태를 확인합니다.
```bash
kubectl get svc/istio-ingressgateway -n istio-system
```
해당 서비스의 타입은 ClusterIP이며, External-IP 값은 `none` 인 것을 확인할 수 있습니다.
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway ClusterIP 10.103.72.5 <none> 15021/TCP,80/TCP,443/TCP,31400/TCP,15443/TCP 4h21m
```
type 을 LoadBalancer 로 변경하고 원하는 IP 주소를 입력하고 싶은 경우 loadBalancerIP 항목을 추가합니다.
추가 하지 않을 경우에는 위에서 설정한 IP 주소풀에서 순차적으로 IP 주소가 배정됩니다.
```bash
kubectl edit svc/istio-ingressgateway -n istio-system
```
```bash
spec:
clusterIP: 10.103.72.5
clusterIPs:
- 10.103.72.5
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: status-port
port: 15021
protocol: TCP
targetPort: 15021
- name: http2
port: 80
protocol: TCP
targetPort: 8080
- name: https
port: 443
protocol: TCP
targetPort: 8443
- name: tcp
port: 31400
protocol: TCP
targetPort: 31400
- name: tls
port: 15443
protocol: TCP
targetPort: 15443
selector:
app: istio-ingressgateway
istio: ingressgateway
sessionAffinity: None
type: LoadBalancer # Change ClusterIP to LoadBalancer
loadBalancerIP: 192.168.35.100 # Add IP
status:
loadBalancer: {}
```
다시 확인을 해보면 External-IP 값이 `192.168.35.100` 인 것을 확인합니다.
```bash
kubectl get svc/istio-ingressgateway -n istio-system
```
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.103.72.5 192.168.35.100 15021:31054/TCP,80:30853/TCP,443:30443/TCP,31400:30012/TCP,15443:31650/TCP 5h1m
```
Web Browser 를 열어 [http://192.168.35.100](http://192.168.35.100) 으로 접속하여, 다음과 같은 화면이 출력되는 것을 확인합니다.

### minio Dashboard
먼저 minio 의 Dashboard 를 제공하는 kubeflow 네임스페이스의 minio-service 서비스의 타입을 LoadBalancer로 변경하여 MetalLB로부터 로드 벨런싱 기능을 제공받기 전에, 현재 상태를 확인합니다.
```bash
kubectl get svc/minio-service -n kubeflow
```
해당 서비스의 타입은 ClusterIP이며, External-IP 값은 `none` 인 것을 확인할 수 있습니다.
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
minio-service ClusterIP 10.109.209.87 <none> 9000/TCP 5h14m
```
type 을 LoadBalancer 로 변경하고 원하는 IP 주소를 입력하고 싶은 경우 loadBalancerIP 항목을 추가합니다.
추가 하지 않을 경우에는 위에서 설정한 IP 주소풀에서 순차적으로 IP 주소가 배정됩니다.
```bash
kubectl edit svc/minio-service -n kubeflow
```
```bash
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"application-crd-id":"kubeflow-pipelines"},"name":"minio-ser>
creationTimestamp: "2022-01-05T08:44:23Z"
labels:
application-crd-id: kubeflow-pipelines
name: minio-service
namespace: kubeflow
resourceVersion: "21120"
uid: 0053ee28-4f87-47bb-ad6b-7ad68aa29a48
spec:
clusterIP: 10.109.209.87
clusterIPs:
- 10.109.209.87
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: http
port: 9000
protocol: TCP
targetPort: 9000
selector:
app: minio
application-crd-id: kubeflow-pipelines
sessionAffinity: None
type: LoadBalancer # Change ClusterIP to LoadBalancer
loadBalancerIP: 192.168.35.101 # Add IP
status:
loadBalancer: {}
```
다시 확인을 해보면 External-IP 값이 `192.168.35.101` 인 것을 확인할 수 있습니다.
```bash
kubectl get svc/minio-service -n kubeflow
```
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
minio-service LoadBalancer 10.109.209.87 192.168.35.101 9000:31371/TCP 5h21m
```
Web Browser 를 열어 [http://192.168.35.101:9000](http://192.168.35.101:9000) 으로 접속하여, 다음과 같은 화면이 출력되는 것을 확인합니다.

### mlflow Dashboard
먼저 mlflow 의 Dashboard 를 제공하는 mlflow-system 네임스페이스의 mlflow-server-service 서비스의 타입을 LoadBalancer로 변경하여 MetalLB로부터 로드 벨런싱 기능을 제공받기 전에, 현재 상태를 확인합니다.
```bash
kubectl get svc/mlflow-server-service -n mlflow-system
```
해당 서비스의 타입은 ClusterIP이며, External-IP 값은 `none` 인 것을 확인할 수 있습니다.
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mlflow-server-service ClusterIP 10.111.173.209 <none> 5000/TCP 4m50s
```
type 을 LoadBalancer 로 변경하고 원하는 IP 주소를 입력하고 싶은 경우 loadBalancerIP 항목을 추가합니다.
추가 하지 않을 경우에는 위에서 설정한 IP 주소풀에서 순차적으로 IP 주소가 배정됩니다.
```bash
kubectl edit svc/mlflow-server-service -n mlflow-system
```
```bash
apiVersion: v1
kind: Service
metadata:
annotations:
meta.helm.sh/release-name: mlflow-server
meta.helm.sh/release-namespace: mlflow-system
creationTimestamp: "2022-01-07T04:00:19Z"
labels:
app.kubernetes.io/managed-by: Helm
name: mlflow-server-service
namespace: mlflow-system
resourceVersion: "276246"
uid: e5d39fb7-ad98-47e7-b512-f9c673055356
spec:
clusterIP: 10.111.173.209
clusterIPs:
- 10.111.173.209
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- port: 5000
protocol: TCP
targetPort: 5000
selector:
app.kubernetes.io/name: mlflow-server
sessionAffinity: None
type: LoadBalancer # Change ClusterIP to LoadBalancer
loadBalancerIP: 192.168.35.102 # Add IP
status:
loadBalancer: {}
```
다시 확인을 해보면 External-IP 값이 `192.168.35.102` 인 것을 확인할 수 있습니다.
```bash
kubectl get svc/mlflow-server-service -n mlflow-system
```
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mlflow-server-service LoadBalancer 10.111.173.209 192.168.35.102 5000:32287/TCP 6m11s
```
Web Browser 를 열어 [http://192.168.35.102:5000](http://192.168.35.102:5000) 으로 접속하여, 다음과 같은 화면이 출력되는 것을 확인합니다.

### Grafana Dashboard
먼저 Grafana 의 Dashboard 를 제공하는 seldon-system 네임스페이스의 seldon-core-analytics-grafana 서비스의 타입을 LoadBalancer로 변경하여 MetalLB로부터 로드 벨런싱 기능을 제공받기 전에, 현재 상태를 확인합니다.
```bash
kubectl get svc/seldon-core-analytics-grafana -n seldon-system
```
해당 서비스의 타입은 ClusterIP이며, External-IP 값은 `none` 인 것을 확인할 수 있습니다.
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
seldon-core-analytics-grafana ClusterIP 10.109.20.161 <none> 80/TCP 94s
```
type 을 LoadBalancer 로 변경하고 원하는 IP 주소를 입력하고 싶은 경우 loadBalancerIP 항목을 추가합니다.
추가 하지 않을 경우에는 위에서 설정한 IP 주소풀에서 순차적으로 IP 주소가 배정됩니다.
```bash
kubectl edit svc/seldon-core-analytics-grafana -n seldon-system
```
```bash
apiVersion: v1
kind: Service
metadata:
annotations:
meta.helm.sh/release-name: seldon-core-analytics
meta.helm.sh/release-namespace: seldon-system
creationTimestamp: "2022-01-07T04:16:47Z"
labels:
app.kubernetes.io/instance: seldon-core-analytics
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: grafana
app.kubernetes.io/version: 7.0.3
helm.sh/chart: grafana-5.1.4
name: seldon-core-analytics-grafana
namespace: seldon-system
resourceVersion: "280605"
uid: 75073b78-92ec-472c-b0d5-240038ea8fa5
spec:
clusterIP: 10.109.20.161
clusterIPs:
- 10.109.20.161
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: service
port: 80
protocol: TCP
targetPort: 3000
selector:
app.kubernetes.io/instance: seldon-core-analytics
app.kubernetes.io/name: grafana
sessionAffinity: None
type: LoadBalancer # Change ClusterIP to LoadBalancer
loadBalancerIP: 192.168.35.103 # Add IP
status:
loadBalancer: {}
```
다시 확인을 해보면 External-IP 값이 `192.168.35.103` 인 것을 확인할 수 있습니다.
```bash
kubectl get svc/seldon-core-analytics-grafana -n seldon-system
```
```bash
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
seldon-core-analytics-grafana LoadBalancer 10.109.20.161 192.168.35.103 80:31191/TCP 5m14s
```
Web Browser 를 열어 [http://192.168.35.103:80](http://192.168.35.103:80) 으로 접속하여, 다음과 같은 화면이 출력되는 것을 확인합니다.

================================================
FILE: docs/appendix/pyenv.md
================================================
---
title: "1. Python 가상환경 설치"
sidebar_position: 1
---
## 파이썬 가상환경
Python 환경을 사용하다 보면 여러 버전의 Python 환경을 사용하고 싶은 경우나, 여러 프로젝트별 패키지 버전을 따로 관리하고 싶은 경우가 발생합니다.
이처럼 Python 환경 혹은 Python Package 환경을 가상화하여 관리하는 것을 쉽게 도와주는 도구로는 pyenv, conda, virtualenv, venv 등이 존재합니다.
이 중 *모두의 MLOps*에서는 [pyenv](https://github.com/pyenv/pyenv)와 [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)를 설치하는 방법을 다룹니다.
pyenv는 Python 버전을 관리하는 것을 도와주며, pyenv-virtualenv는 pyenv의 plugin으로써 파이썬 패키지 환경을 관리하는 것을 도와줍니다.
## pyenv 설치
### Prerequisites
운영 체제별로 Prerequisites가 다릅니다. [다음 페이지](https://github.com/pyenv/pyenv/wiki#suggested-build-environment)를 참고하여 필수 패키지들을 설치해주시기 바랍니다.
### 설치 - macOS
1. pyenv, pyenv-virtualenv 설치
```bash
brew update
brew install pyenv
brew install pyenv-virtualenv
```
2. pyenv 설정
macOS의 경우 카탈리나 버전 이후 기본 shell이 zsh로 변경되었기 때문에 zsh을 사용하는 경우를 가정하였습니다.
```bash
echo 'eval "$(pyenv init -)"' >> ~/.zshrc
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.zshrc
source ~/.zshrc
```
pyenv 명령이 정상적으로 수행되는지 확인합니다.
```bash
pyenv --help
```
```bash
$ pyenv --help
Usage: pyenv <command> [<args>]
Some useful pyenv commands are:
--version Display the version of pyenv
activate Activate virtual environment
commands List all available pyenv commands
deactivate Deactivate virtual environment
exec Run an executable with the selected Python version
global Set or show the global Python version(s)
help Display help for a command
hooks List hook scripts for a given pyenv command
init Configure the shell environment for pyenv
install Install a Python version using python-build
local Set or show the local application-specific Python version(s)
prefix Display prefix for a Python version
rehash Rehash pyenv shims (run this after installing executables)
root Display the root directory where versions and shims are kept
shell Set or show the shell-specific Python version
shims List existing pyenv shims
uninstall Uninstall a specific Python version
version Show the current Python version(s) and its origin
version-file Detect the file that sets the current pyenv version
version-name Show the current Python version
version-origin Explain how the current Python version is set
versions List all Python versions available to pyenv
virtualenv Create a Python virtualenv using the pyenv-virtualenv plugin
virtualenv-delete Uninstall a specific Python virtualenv
virtualenv-init Configure the shell environment for pyenv-virtualenv
virtualenv-prefix Display real_prefix for a Python virtualenv version
virtualenvs List all Python virtualenvs found in `$PYENV_ROOT/versions/*'.
whence List all Python versions that contain the given executable
which Display the full path to an executable
See `pyenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/pyenv/pyenv#readme
```
### 설치 - Ubuntu
1. pyenv, pyenv-virtualenv 설치
```bash
curl https://pyenv.run | bash
```
다음과 같은 내용이 출력되면 정상적으로 설치된 것을 의미합니다.
```bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- 0 0 0 0 0 0 0 0 --:--:-- --:--:-- 100 270 100 270 0 0 239 0 0:00:01 0:00:01 --:--:-- 239
Cloning into '/home/mlops/.pyenv'...
r
...
중략...
...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 10 (delta 1), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (10/10), 2.92 KiB | 2.92 MiB/s, done.
WARNING: seems you still have not added 'pyenv' to the load path.
# See the README for instructions on how to set up
# your shell environment for Pyenv.
# Load pyenv-virtualenv automatically by adding
# the following to ~/.bashrc:
eval "$(pyenv virtualenv-init -)"
```
2. pyenv 설정
기본 shell로 bash shell을 사용하는 경우를 가정하였습니다.
bash에서 pyenv와 pyenv-virtualenv 를 사용할 수 있도록 설정합니다.
```bash
sudo vi ~/.bashrc
```
다음 문자열을 입력한 후 저장합니다.
```bash
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
```
shell을 restart 합니다.
```bash
exec $SHELL
```
pyenv 명령이 정상적으로 수행되는지 확인합니다.
```bash
pyenv --help
```
다음과 같은 메시지가 출력되면 정상적으로 설정된 것을 의미합니다.
```bash
$ pyenv
pyenv 2.2.2
Usage: pyenv <command> [<args>]
Some useful pyenv commands are:
--version Display the version of pyenv
activate Activate virtual environment
commands List all available pyenv commands
deactivate Deactivate virtual environment
doctor Verify pyenv installation and development tools to build pythons.
exec Run an executable with the selected Python version
global Set or show the global Python version(s)
help Display help for a command
hooks List hook scripts for a given pyenv command
init Configure the shell environment for pyenv
install Install a Python version using python-build
local Set or show the local application-specific Python version(s)
prefix Display prefix for a Python version
rehash Rehash pyenv shims (run this after installing executables)
root Display the root directory where versions and shims are kept
shell Set or show the shell-specific Python version
shims List existing pyenv shims
uninstall Uninstall a specific Python version
version Show the current Python version(s) and its origin
version-file Detect the file that sets the current pyenv version
version-name Show the current Python version
version-origin Explain how the current Python version is set
versions List all Python versions available to pyenv
virtualenv Create a Python virtualenv using the pyenv-virtualenv plugin
virtualenv-delete Uninstall a specific Python virtualenv
virtualenv-init Configure the shell environment for pyenv-virtualenv
virtualenv-prefix Display real_prefix for a Python virtualenv version
virtualenvs List all Python virtualenvs found in `$PYENV_ROOT/versions/*'.
whence List all Python versions that contain the given executable
which Display the full path to an executable
See `pyenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/pyenv/pyenv#readme
```
## pyenv 사용
### Python 버전 설치
`pyenv install <Python-Version>` 명령을 통해 원하는 파이썬 버전을 설치할 수 있습니다.
이번 페이지에서는 예시로 kubeflow에서 기본으로 사용하는 파이썬 3.7.12 버전을 설치하겠습니다.
```bash
pyenv install 3.7.12
```
정상적으로 설치되면 다음과 같은 메시지가 출력됩니다.
```bash
$ pyenv install 3.7.12
Downloading Python-3.7.12.tar.xz...
-> https://www.python.org/ftp/python/3.7.12/Python-3.7.12.tar.xz
Installing Python-3.7.12...
patching file Doc/library/ctypes.rst
patching file Lib/test/test_unicode.py
patching file Modules/_ctypes/_ctypes.c
patching file Modules/_ctypes/callproc.c
patching file Modules/_ctypes/ctypes.h
patching file setup.py
patching file 'Misc/NEWS.d/next/Core and Builtins/2020-06-30-04-44-29.bpo-41100.PJwA6F.rst'
patching file Modules/_decimal/libmpdec/mpdecimal.h
Installed Python-3.7.12 to /home/mlops/.pyenv/versions/3.7.12
```
### Python 가상환경 생성
`pyenv virtualenv <Installed-Python-Version> <가상환경-이름>` 명령을 통해 원하는 파이썬 버전의 파이썬 가상환경을 생성할 수 있습니다.
예시로 Python 3.7.12 버전의 `demo`라는 이름의 Python 가상환경을 생성하겠습니다.
```bash
pyenv virtualenv 3.7.12 demo
```
```bash
$ pyenv virtualenv 3.7.12 demo
Looking in links: /tmp/tmpffqys0gv
Requirement already satisfied: setuptools in /home/mlops/.pyenv/versions/3.7.12/envs/demo/lib/python3.7/site-packages (47.1.0)
Requirement already satisfied: pip in /home/mlops/.pyenv/versions/3.7.12/envs/demo/lib/python3.7/site-packages (20.1.1)
```
### Python 가상환경 사용
`pyenv activate <가상환경 이름>` 명령을 통해 위와 같은 방식으로 생성한 가상환경을 사용할 수 있습니다.
예시로는 `demo`라는 이름의 Python 가상환경을 사용하겠습니다.
```bash
pyenv activate demo
```
다음과 같이 현재 가상환경의 정보가 shell의 맨 앞에 출력되는 것을 확인할 수 있습니다.
Before
```bash
mlops@ubuntu:~$ pyenv activate demo
```
After
```bash
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.
(demo) mlops@ubuntu:~$
```
### Python 가상환경 비활성화
`source deactivate` 명령을 통해 현재 사용 중인 가상환경을 비활성화할 수 있습니다.
```bash
source deactivate
```
Before
```bash
(demo) mlops@ubuntu:~$ source deactivate
```
After
```bash
mlops@ubuntu:~$
```
================================================
FILE: docs/further-readings/_category_.json
================================================
{
"label": "Further Readings",
"position": 8,
"link": {
"type": "generated-index"
}
}
================================================
FILE: docs/further-readings/info.md
================================================
---
title: "다루지 못한 것들"
date: 2021-12-21
lastmod: 2021-12-21
---
## MLOps Component
[MLOps Concepts](../introduction/component.md)에서 다루었던 컴포넌트를 도식화하면 다음과 같습니다.

이 중 *모두의 MLOps* 에서 다룬 기술 스택들은 다음과 같습니다.

보시는 것처럼 아직 우리가 다루지 못한 많은 MLOps 컴포넌트들이 있습니다.
시간 관계상 이번에 모두 다루지는 못했지만, 만약 필요하다면 다음과 같은 오픈소스들을 먼저 참고해보면 좋을 것 같습니다.

세부 내용은 다음과 같습니다.
| Mgmt. | Component | Open Soruce |
| -------------------------- | --------------------------- | ------------------------------------- |
| Data Mgmt. | Collection | [Kafka](https://kafka.apache.org/) |
| | Validation | [Beam](https://beam.apache.org/) |
| | Feature Store | [Flink](https://flink.apache.org/) |
| ML Model Dev. & Experiment | Modeling | [Jupyter](https://jupyter.org/) |
| | Analysis & Experiment Mgmt. | [MLflow](https://mlflow.org/) |
| | HPO Tuning & AutoML | [Katib](https://github.com/kubeflow/katib) |
| Deploy Mgmt. | Serving Framework | [Seldon Core](https://docs.seldon.io/projects/seldon-core/en/latest/index.html) |
| | A/B Test | [Iter8](https://iter8.tools/) |
| | Monitoring | [Grafana](https://grafana.com/oss/grafana/), [Prometheus](https://prometheus.io/) |
| Process Mgmt. | pipeline | [Kubeflow](https://www.kubeflow.org/) |
| | CI/CD | [Github Action](https://docs.github.com/en/actions) |
| | Continuous Training | [Argo Events](https://argoproj.github.io/events/) |
| Platform Mgmt. | Configuration Mgmt. | [Consul](https://www.consul.io/) |
| | Code Version Mgmt. | [Github](https://github.com/), [Minio](https://min.io/) |
| | Logging | (EFK) [Elastic Search](https://www.elastic.co/kr/elasticsearch/), [Fluentd](https://www.fluentd.org/), [Kibana](https://www.elastic.co/kr/kibana/) |
| | Resource Mgmt. | [Kubernetes](https://kubernetes.io/) |
================================================
FILE: docs/introduction/_category_.json
================================================
{
"label": "Introduction",
"position": 1,
"link": {
"type": "generated-index"
}
}
================================================
FILE: docs/introduction/component.md
================================================
---
title : "3. Components of MLOps"
description: "Describe MLOps Components"
sidebar_position: 3
date: 2021-12-03
lastmod: 2021-12-10
contributors: ["Youngcheol Jang"]
---
## Practitioners guide to MLOps
2021년 5월에 발표된 구글의 [white paper : Practitioners guide to MLOps: A framework for continuous delivery and automation of machine learning](https://services.google.com/fh/files/misc/practitioners_guide_to_mlops_whitepaper.pdf)에서는 MLOps의 핵심 기능들로 다음과 같은 것들을 언급하였습니다.

각 기능이 어떤 역할을 하는지 살펴보겠습니다.
### 1. Experimentation
실험(Experimentation)은 머신러닝 엔지니어들이 데이터를 분석하고, 프로토타입 모델을 만들며 학습 기능을 구현할 수 있도록 하는 다음과 같은 기능을 제공합니다.
- 깃(Git)과 같은 버전 컨트롤 도구와 통합된 노트북(Jupyter Notebook) 환경 제공
- 사용한 데이터, 하이퍼 파라미터, 평가 지표를 포함한 실험 추적 기능 제공
- 데이터와 모델에 대한 분석 및 시각화 기능 제공
### 2. Data Processing
데이터 처리(Data Processing)는 머신러닝 모델 개발 단계, 지속적인 학습(Continuous Training) 단계, 그리고 API 배포(API Deployment) 단계에서 많은 양의 데이터를 사용할 수 있게 해 주는 다음과 같은 기능을 제공합니다.
- 다양한 데이터 소스와 서비스에 호환되는 데이터 커넥터(connector) 기능 제공
- 다양한 형태의 데이터와 호환되는 데이터 인코더(encoder) & 디코더(decoder) 기능 제공
- 다양한 형태의 데이터에 대한 데이터 변환과 피처 엔지니어링(feature engineering) 기능 제공
- 학습과 서빙을 위한 확장 가능한 배치, 스트림 데이터 처리 기능 제공
### 3. Model training
모델 학습(Model training)은 모델 학습을 위한 알고리즘을 효율적으로 실행시켜주는 다음과 같은 기능을 제공합니다.
- ML 프레임워크의 실행을 위한 환경 제공
- 다수의 GPU / 분산 학습 사용을 위한 분산 학습 환경 제공
- 하이퍼 파라미터 튜닝과 최적화 기능 제공
### 4. Model evaluation
모델 평가(Model evaluation)는 실험 환경과 상용 환경에서 동작하는 모델의 성능을 관찰할 수 있는 다음과 같은 기능을 제공합니다.
- 평가 데이터에 대한 모델 성능 평가 기능
- 서로 다른 지속 학습 실행 결과에 대한 예측 성능 추적
- 서로 다른 모델의 성능 비교와 시각화
- 해석할 수 있는 AI 기술을 이용한 모델 출력 해석 기능 제공
### 5. Model serving
모델 서빙(Model serving)은 상용 환경에 모델을 배포하고 서빙하기 위한 다음과 같은 기능들을 제공합니다.
- 저 지연 추론과 고가용성 추론 기능 제공
- 다양한 ML 모델 서빙 프레임워크 지원(Tensorflow Serving, TorchServe, NVIDIA Triton, Scikit-learn, XGBoost. etc)
- 복잡한 형태의 추론 루틴 기능 제공, 예를 들어 전처리(preprocess) 또는 후처리(postprocess) 기능과 최종 결과를 위해 다수의 모델이 사용되는 경우를 말합니다.
- 순간적으로 치솟는 추론 요청을 처리하기 위한 오토 스케일링(autoscaling) 기능 제공
- 추론 요청과 추론 결과에 대한 로깅 기능 제공
### 6. Online experimentation
온라인 실험(Online experimentation)은 새로운 모델이 생성되었을 때, 이 모델을 배포하면 어느 정도의 성능을 보일 것인지 검증하는 기능을 제공합니다. 이 기능은 새 모델을 배포하는 것까지 연동하기 위해 모델 저장소(Model Registry)와 연동되어야 합니다.
- 카나리(canary) & 섀도(shadow) 배포 기능 제공
- A/B 테스트 기능 제공
- 멀티 암드 밴딧(Multi-armed bandit) 테스트 기능 제공
### 7. Model Monitoring
모델 모니터링(Model Monitoring)은 상용 환경에 배포된 모델이 정상적으로 동작하고 있는지를 모니터링하는 기능을 제공합니다. 예를 들어 모델의 성능이 떨어져 업데이트가 필요한지에 대한 정보 등을 제공합니다.
### 8. ML Pipeline
머신러닝 파이프라인(ML Pipeline)은 상용 환경에서 복잡한 ML 학습과 추론 작업을 구성하고 제어하고 자동화하기 위한 다음과 같은 기능을 제공합니다.
- 다양한 이벤트를 소스를 통한 파이프라인 실행 기능
- 파이프라인 파라미터와 생성되는 산출물 관리를 위한 머신러닝 메타데이터 추적과 연동 기능
- 일반적인 머신러닝 작업을 위한 내장 컴포넌트 지원과 사용자가 직접 구현한 컴포넌트에 대한 지원 기능
- 서로 다른 실행 환경 제공 기능
### 9. Model Registry
모델 저장소(Model Registry)는 머신러닝 모델의 생명 주기(Lifecycle)을 중앙 저장소에서 관리할 수 있게 해 주는 기능을 제공합니다.
- 학습된 모델 그리고 배포된 모델에 대한 등록, 추적, 버저닝 기능 제공
- 배포를 위해 필요한 데이터와 런타임 패키지들에 대한 정보 저장 기능
### 10. Dataset and Feature Repository
- 데이터에 대한 공유, 검색, 재사용 그리고 버전 관리 기능
- 이벤트 스트리밍 및 온라인 추론 작업에 대한 실시간 처리 및 저 지연 서빙 기능
- 사진, 텍스트, 테이블 형태의 데이터와 같은 다양한 형태의 데이터 지원 기능
### 11. ML Metadata and Artifact Tracking
MLOps의 각 단계에서는 다양한 형태의 산출물들이 생성됩니다. ML 메타데이터는 이런 산출물들에 대한 정보를 의미합니다.
ML 메타데이터와 산출물 관리는 산출물의 위치, 타입, 속성, 그리고 관련된 실험(experiment)에 대한 정보를 관리하기 위해 다음과 같은 기능들을 제공합니다.
- ML 산출물에 대한 히스토리 관리 기능
- 실험과 파이프라인 파라미터 설정에 대한 추적, 공유 기능
- ML 산출물에 대한 저장, 접근, 시각화, 다운로드 기능 제공
- 다른 MLOps 기능과의 통합 기능 제공
================================================
FILE: docs/introduction/intro.md
================================================
---
title : "1. What is MLOps?"
description: "Introduction to MLOps"
sidebar_position: 1
date: 2021-1./img to MLOps"
lastmod: 2022-03-05
contributors: ["Jongseob Jeon"]
---
## Machine Learning Project
2012년 Alexnet 이후 CV, NLP를 비롯하여 데이터가 존재하는 도메인이라면 어디서든 머신러닝과 딥러닝을 도입하고자 하였습니다.
딥러닝과 머신러닝은 AI라는 단어로 묶이며 불렸고 많은 매체에서 AI의 필요성을 외쳤습니다. 그리고 무수히 많은 기업에서 머신러닝과 딥러닝을 이용한 수많은 프로젝트를 진행하였습니다. 하지만 그 결과는 어떻게 되었을까요?
엘리먼트 AI의 음병찬 동북아 지역 총괄책임자는 [*"10개 기업에 AI 프로젝트를 시작한다면 그중 9개는 컨셉검증(POC)만 하다 끝난다"*](https://zdnet.co.kr/view/?no=20200611062002)고 말했습니다.
이처럼 많은 프로젝트에서 머신러닝과 딥러닝은 이 문제를 풀 수 있을 것 같다는 가능성만을 보여주고 사라졌습니다. 그리고 이 시기쯤에 [AI에 다시 겨울](https://www.aifutures.org/2021/ai-winter-is-coming/)이 다가오고 있다는 전망도 나오기 시작했습니다.
왜 프로젝트 대부분이 컨셉검증(POC) 단계에서 끝났을까요?
머신러닝과 딥러닝 코드만으로는 실제 서비스를 운영할 수 없기 때문입니다.
실제 서비스 단계에서 머신러닝과 딥러닝의 코드가 차지하는 부분은 생각보다 크지 않기 때문에, 단순히 모델의 성능만이 아닌 다른 많은 부분을 고려해야 합니다.
구글은 이런 문제를 2015년 [Hidden Technical Debt in Machine Learning Systems](https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf)에서 지적한 바 있습니다.
하지만 이 논문이 나올 당시에는 아직 많은 머신러닝 엔지니어들이 딥러닝과 머신러닝의 가능성을 입증하기 바쁜 시기였기 때문에, 논문이 지적하는 바에 많은 주의를 기울이지는 않았습니다.
그리고 몇 년이 지난 후 머신러닝과 딥러닝은 가능성을 입증해내어, 이제 사람들은 실제 서비스에 적용하고자 했습니다.
하지만 곧 많은 사람이 실제 서비스는 쉽지 않다는 것을 깨달았습니다.
## Devops
MLOps는 이전에 없던 새로운 개념이 아니라 DevOps라고 불리는 개발 방법론에서 파생된 단어입니다. 그렇기에 DevOps를 이해한다면 MLOps를 이해하는 데 도움이 됩니다.
### DevOps
DevOps는 Development(개발)와 Operations(운영)의 합성어로 소프트웨어의 개발(Development)과 운영(Operations)의 합성어로서 소프트웨어 개발자와 정보기술 전문가 간의 소통, 협업 및 통합을 강조하는 개발 환경이나 문화를 말합니다.
DevOps의 목적은 소프트웨어 개발 조직과 운영 조직간의 상호 의존적 대응이며 조직이 소프트웨어 제품과 서비스를 빠른 시간에 개발 및 배포하는 것을 목적으로 합니다.
### Silo Effect
그럼 간단한 상황 설명을 통해 DevOps가 왜 필요한지 알아보도록 하겠습니다.
서비스 초기에는 지원하는 기능이 많지 않으며 팀 또는 회사의 규모가 작습니다. 이때에는 개발팀과 운영팀의 구분이 없거나 작은 규모의 팀으로 구분되어 있습니다. 핵심은 규모가 작다는 것에 있습니다. 이때는 서로 소통할 수 있는 접점이 많고, 집중해야 하는 서비스가 적기 때문에 빠르게 서비스를 개선해 나갈 수 있습니다.
하지만 서비스의 규모가 커질수록 개발팀과 운영팀은 분리되고 서로 소통할 수 있는 채널의 물리적인 한계가 오게 됩니다. 예를 들어서 다른 팀과 함께하는 미팅에 팀원 전체가 미팅을 하는 것이 아니라 각 팀의 팀장 혹은 소수의 시니어만 참석하여 미팅을 진행하게 됩니다. 이런 소통 채널의 한계는 필연적으로 소통의 부재로 이어지게 됩니다. 그러다 보면 개발팀은 새로운 기능들을 계속해서 개발하고 운영팀 입장에서는 개발팀에서 개발한 기능이 배포 시 장애를 일으키는 등 여러 문제가 생기게 됩니다.
위와 같은 상황이 반복되면 조직 이기주의라고 불리는 사일로 현상이 생길 수 있습니다.

> 사일로(silo)는 곡식이나 사료를 저장하는 굴뚝 모양의 창고를 의미한다. 사일로는 독립적으로 존재하며 저장되는 물품이 서로 섞이지 않도록 철저히 관리할 수 있도록 도와준다.
> 사일로 효과(Organizational Silos Effect)는 조직 부서 간에 서로 협력하지 않고 내부 이익만을 추구하는 현상을 의미한다. 조직 내에서 개별 부서끼리 서로 담을 쌓고 각자의 이익에만 몰두하는 부서 이기주의를 일컫는다.
사일로 현상은 서비스 품질의 저하로 이어지게 됩니다. 이러한 사일로 현상을 해결하기 위해 나온 것이 바로 DevOps입니다.
### CI/CD
Continuous Integration(CI) 와 Continuous Delivery (CD)는 개발팀과 운영팀의 장벽을 해제하기 위한 구체적인 방법입니다.

이 방법을 통해서 개발팀에서는 운영팀의 환경을 이해하고 개발팀에서 개발 중인 기능이 정상적으로 배포까지 이어질 수 있는지 확인합니다. 운영팀은 검증된 기능 또는 개선된 제품을 더 자주 배포해 고객의 제품 경험을 상승시킵니다.
앞에서 설명한 내용을 종합하자면 DevOps는 개발팀과 운영팀 간의 문제가 있었고 이를 해결하기 위한 방법론입니다.
## MLOps
### 1) ML+Ops
MLOps는 Machine Learning 과 Operations의 합성어로 DevOps에서 Dev가 ML로 바뀌었습니다. 이제 앞에서 살펴본 DevOps를 통해 MLOps가 무엇인지 짐작해 볼 수 있습니다.
“MLOps는 머신러닝팀과 운영팀의 문제를 해결하기 위한 방법입니다.”
이 말은 머신러닝팀과 운영팀 사이에 문제가 발생했다는 의미입니다. 그럼 왜 머신러닝팀과 운영팀에는 문제가 발생했을까요? 두 팀 간의 문제를 알아보기 위해서 추천시스템을 예시로 알아보겠습니다.
#### Rule Based
처음 추천시스템을 만드는 경우 간단한 규칙을 기반으로 아이템을 추천합니다. 예를 들어서 1주일간 판매량이 가장 많은 순서대로 보여주는 식의 방식을 이용합니다. 이 방식으로 모델을 정한다면 특별한 이유가 없는 이상 모델의 수정이 필요 없습니다.
#### Machine Learning
서비스의 규모가 조금 커지고 로그 데이터가 많이 쌓인다면 이를 이용해 아이템 기반 혹은 유저 기반의 머신러닝 모델을 생성합니다. 이때 모델은 정해진 주기에 따라 모델을 재학습 후 재배포합니다.
#### Deep Learning
개인화 추천에 대한 요구가 더 커지고 더 좋은 성능을 내는 모델을 필요해질 경우 딥러닝을 이용한 모델을 개발하기 시작합니다. 이때 만드는 모델은 머신러닝과 같이 정해진 주기에 따라 모델을 재학습 후 재배포합니다.

위에서 설명한 것을 x축을 모델의 복잡도, y축을 모델의 성능으로 두고 그래프로 표현한다면 다음과 같이 복잡도가 올라갈 때 모델의 성능이 올라가는 상승 관계를 갖습니다. 머신러닝에서 딥러닝으로 넘어갈 머신러닝 팀이 새로 생기게 됩니다.
만약 관리해야할 모델이 적다면 서로 협업을 통해서 충분히 해결할 수 있지만 개발해야 할 모델이 많아진다면 DevOps의 경우와 같이 사일로 현상이 나타나게 됩니다.
DevOps의 목표와 맞춰서 생각해보면 MLOps의 목표는 개발한 모델이 정상적으로 배포될 수 있는지 테스트하는 것입니다. 개발팀에서 개발한 기능이 정상적으로 배포될 수 있는지 확인하는 것이 DevOps의 목표였다면, MLOps의 목표는 머신러닝 팀에서 개발한 모델이 정상적으로 배포될 수 있는지 확인하는 것입니다.
### 2) ML -> Ops
하지만 최근 나오고 있는 MLOps 관련 제품과 설명을 보면 꼭 앞에서 설명한 목표만을 대상으로 하고 있지 않습니다.
어떤 경우에는 머신러닝 팀에서 만든 모델을 이용해 직접 운영을 할 수 있도록 도와주려고 합니다. 이러한 니즈는 최근 머신러닝 프로젝트가 진행되는 과정에서 알 수 있습니다.
추천시스템의 경우 운영에서 간단한 모델부터 시작해 운영할 수 있었습니다. 하지만 자연어, 이미지와 같은 곳에서는 규칙 기반의 모델보다는 딥러닝을 이용해 주어진 태스크를 해결할 수 있는지 검증(POC)를 선행하는 경우가 많습니다. 검증이 끝난 프로젝트는 이제 서비스를 위한 운영 환경을 개발하기 시작합니다. 하지만 머신러닝 팀 내의 자체 역량으로는 이 문제를 해결하기 쉽지 않습니다. 이를 해결하기 위해서 MLOps가 필요한 경우도 있습니다.
### 3) 결론
요약하자면 MLOps는 두 가지 목표가 있습니다.
앞에서 설명한 MLOps는 ML+Ops 로 두 팀의 생산성 향상을 위한 것이였습니다.
반면, 뒤에서 설명한 것은 ML->Ops 로 머신러닝 팀에서 직접 운영을 할 수 있도록 도와주는 것을 말합니다.
================================================
FILE: docs/introduction/levels.md
================================================
---
title : "2. Levels of MLOps"
description: "Levels of MLOps"
sidebar_position: 2
date: 2021-12-03
lastmod: 2022-03-05
contributors: ["Jongseob Jeon", "Chanmin Cho"]
---
이번 페이지에서는 구글에서 발표한 MLOps의 단계를 보며 MLOps의 핵심 기능은 무엇인지 알아 보겠습니다.
## Hidden Technical Debt in ML System
구글은 무려 2015년부터 MLOps의 필요성을 말했습니다. Hidden Technical Debt in Machine Learning Systems 은 그런 구글의 생각을 담은 논문입니다.

이 논문의 핵심은 바로 머신러닝을 이용한 제품을 만드는데 있어서 머신러닝 코드는 전체 시스템을 구성하는데 있어서 아주 일부일 뿐이라는 것입니다.

구글은 이 논문을 더 발전시켜서 MLOps라는 용어를 만들어 확장시켰습니다. 더 자세한 내용은 [구글 클라우드 홈페이지](https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning)에서 더 자세한 내용을 확인할 수 있습니다. 이번 포스트에서는 구글에서 말하는 MLOps란 어떤 것인지에 대해서 설명해보고자 합니다.
구글에서는 MLOps의 발전 단계를 총 3(0~2)단계로 나누었습니다. 각 단계들에 대해 설명하기 앞서 이전 포스트에서 설명했던 개념 중 필요한 부분을 다시 한번 보겠습니다.
머신러닝 모델을 운영하기 위해서는 모델을 개발하는 머신러닝 팀과 배포 및 운영을 담당하는 운영팀이 있습니다. 이 두 팀의 원할한 협업을 위해서 MLOps가 필요하게 되었습니다. 이전에는 간단히 Continuous Integration(CI)/Continuous Deployment(CD)를 통해서 할 수 있다고 하였는데, 어떻게 CI/CD를 하는지에 대해서 알아 보겠습니다.
## 0단계: 수동 프로세스

0단계에서 두 팀은 “모델”을 통해 소통합니다. 머신 러닝팀은 쌓여있는 데이터로 모델을 학습시키고 학습된 모델을 운영팀에게 전달 합니다. 운영팀은 이렇게 전달받은 모델을 배포합니다.

초기의 머신 러닝 모델들은 이 “모델” 중심의 소통을 통해 배포합니다. 그런데 이런 배포 방식은 여러 문제가 있습니다.
예를 들어서 어떤 기능에서는 파이썬 3.7을 쓰고 어떤 기능에서는 파이썬 3.8을 쓴다면 다음과 같은 상황을 자주 목격할 수 있습니다.
이러한 상황이 일어나는 이유는 머신러닝 모델의 특성에 있습니다. 학습된 머신러닝 모델이 동작하기 위해서는 3가지가 필요합니다.
1. 파이썬 코드
2. 학습된 가중치
3. 환경 (패키지, 버전 등)
만약 이 3가지 중 한 가지라도 전달이 잘못 된다면 모델이 동작하지 않거나 예상하지 못한 예측을 할수 있습니다. 그런데 많은 경우 환경이 일치하지 않아서 동작하지 않는 경우가 많습니다. 머신러닝은 다양한 오픈소스를 사용하는데 오픈소스는 특성상 어떤 버전을 쓰는지에 따라서 같은 함수라도 결과가 다를 수 있습니다.
이러한 문제는 서비스 초기에는 관리할 모델이 많지 않기 때문에 금방 해결할 수 있습니다. 하지만 관리하는 기능들이 많아지고 서로 소통에 어려움을 겪게 된다면 성능이 더 좋은 모델을 빠르게 배포할 수 없게 됩니다.
## 1단계: ML 파이프라인 자동화
### Pipeline

그래서 MLOps에서는 “파이프라인(Pipeline)”을 이용해 이러한 문제를 방지하고자 했습니다. MLOps의 파이프라인은 도커와 같은 컨테이너를 이용해 머신러닝 엔지니어가 모델 개발에 사용한 것과 동일한 환경으로 동작되는 것을 보장합니다. 이를 통해서 환경이 달라서 모델이 동작하지 않는 상황을 방지합니다.
그런데 파이프라인은 범용적인 용어로 여러 다양한 태스크에서 사용됩니다. 머신러닝 엔지니어가 작성하는 파이프라인의 역할은 무엇일까요?
머신러닝 엔지니어가 작성하는 파이프라인은 학습된 모델을 생산합니다. 그래서 파이프라인 대신 학습 파이프라인(Training Pipeline)이 더 정확하다고 볼 수 있습니다.
### Continuous Training

그리고 Continuous Training(CT) 개념이 추가됩니다. 그렇다면 CT는 왜 필요할까요?
#### Auto Retrain
Real World에서 데이터는 Data Shift라는 데이터의 분포가 계속해서 변하는 특징이 있습니다. 그래서 과거에 학습한 모델이 시간이 지남에 따라 모델의 성능이 저하되는 문제가 있습니다. 이 문제를 해결하는 가장 간단하고 효과적인 해결책은 바로 최근 데이터를 이용해 모델을 재학습하는 것입니다. 변화된 데이터 분포에 맞춰서 모델을 재학습하면 다시 준수한 성능을 낼 수 있습니다.
#### Auto Deploy
하지만 제조업과 같이 한 공장에서 여러 레시피를 처리하는 경우 무조건 재학습을 하는 것이 좋지 않을 수 도 있습니다. Blind Spot이 대표적인 예입니다.
예를 들어서 자동차 생산 라인에서 모델 A에 대해서 모델을 만들고 이를 이용해 예측을 진행하고 있었습니다. 만약 전혀 다른 모델 B가 들어오면 이전에 보지 못한 데이터 패턴이기 때문에 모델 B에 대해서 새로운 모델을 학습합니다.
이제 모델 B에 대해서 모델을 만들었기 때문에 모델은 예측을 진행할 것 입니다. 그런데 만약 데이터가 다시 모델 A로 바뀐다면 어떻게 할까요?
만약 Retraining 규칙만 있다면 다시 모델 A에 대해서 새로운 모델을 학습하게 됩니다. 그런데 머신러닝 모델이 충분한 성능을 보이기 위해서는 충분한 양의 데이터가 모여야 합니다. Blind Spot이란 이렇게 데이터를 모으기 위해서 모델이 동작하지 않는 구간을 말합니다.
이러한 Blind Spot을 해결하는 방법은 간단할 수 있습니다. 바로 모델 A에 대한 모델이 과거에 있었는지 확인하고 만약 있었다면 새로운 모델을 바로 학습하기 보다는 이 전 모델을 이용해 다시 예측을 하면 이런 Blind Spot을 해결할 수 있습니다. 이렇게 모델와 같은 메타 데이터를 이용해 모델을 자동으로 변환해주는 것을 Auto Deploy라고 합니다.
정리하자면 CT를 위해서는 Auto Retraining과 Auto Deploy 두 가지 기능이 필요합니다. 둘은 서로의 단점을 보완해 계속해서 모델의 성능을 유지할 수 있게 합니다.
### Model Serving

프로덕션 환경에서의 머신러닝 파이프라인은 새로운 데이터에 기반한 최신 모델을 예측 서비스에 지속적으로 배포합니다. 이 과정에서, 훈련되고 검증된 모델을 온라인 예측 서비스에 자동적으로 배포하는 작업이 포함됩니다.
## 2단계: CI/CD 파이프라인의 자동화

2단계의 제목은 CI와 CD의 자동화 입니다. DevOps에서의 CI/CD의 대상은 소스 코드입니다. 그렇다면 MLOps는 어떤 것이 CI/CD의 대상일까요?
MLOps의 CI/CD 대상 또한 소스 코드인 것은 맞지만 조금 더 엄밀히 정의하자면 학습 파이프라인이라고 볼 수 있습니다.
그래서 모델을 학습하는데 있어서 영향이 있는 변화에 대해서 실제로 모델이 정상적으로 학습이 되는지 (CI), 학습된 모델이 정상적으로 동작하는지 (CD)를 확인해야 합니다. 그래서 학습을 하는 코드에 직접적인 수정이 있는 경우에는 CI/CD를 진행해야 합니다.
코드 외에도 사용하는 패키지의 버전, 파이썬의 버전 변경도 CI/CD의 대상입니다. 많은 경우 머신 러닝은 오픈 소스를 이용합니다. 하지만 오픈 소스는 그 특성상 버전이 바뀌었을 때 함수의 내부 로직이 변하는 경우도 있습니다. 물론 어느 정도 버전이 올라 갈 때 이와 관련된 알림을 주지만 한 번에 버전이 크게 바뀐다면 이러한 변화를 모를 수도 있습니다.
그래서 사용하는 패키지의 버전이 변하는 경우에도 CI/CD를 통해 정상적으로 모델이 학습, 동작하는지 확인을 해야 합니다.
================================================
FILE: docs/introduction/why_kubernetes.md
================================================
---
title : "4. Why Kubernetes?"
description: "Reason for using k8s in MLOps"
sidebar_position: 4
date: 2021-12-03
lastmod: 2021-12-10
contributors: ["Jaeyeon Kim"]
---
## MLOps & Kubernetes
그렇다면 MLOps를 이야기할 때, 쿠버네티스(Kubernetes)라는 단어가 항상 함께 들리는 이유가 무엇일까요?
성공적인 MLOps 시스템을 구축하기 위해서는 [MLOps의 구성요소](../introduction/component.md) 에서 설명한 것처럼 다양한 구성 요소들이 필요하지만, 각각의 구성 요소들이 유기적으로 운영되기 위해서는 인프라 레벨에서 수많은 이슈를 해결해야 합니다.
간단하게는 수많은 머신러닝 모델의 학습 요청을 차례대로 실행하는 것, 다른 작업 공간에서도 같은 실행 환경을 보장해야 하는 것, 배포된 서비스에 장애가 생겼을 때 빠르게 대응해야 하는 것 등의 이슈 등을 생각해볼 수 있습니다.
여기서 컨테이너(Container)와 컨테이너 오케스트레이션 시스템(Container Orchestration System)의 필요성이 등장합니다.
쿠버네티스와 같은 컨테이너 오케스트레이션 시스템을 도입하면 실행 환경의 격리와 관리를 효율적으로 수행할 수 있습니다. 컨테이너 오케스트레이션 시스템을 도입한다면, 머신러닝 모델을 개발하고 배포하는 과정에서 다수의 개발자가 소수의 클러스터를 공유하면서 *'1번 클러스터 사용 중이신가요?', 'GPU 사용 중이던 제 프로세스 누가 죽였나요?', '누가 클러스터에 x 패키지 업데이트했나요?'* 와 같은 상황을 방지할 수 있습니다.
## Container
그렇다면 컨테이너란 무엇일까요? 마이크로소프트에서는 컨테이너를 [다음](https://azure.microsoft.com/ko-kr/overview/what-is-a-container/)과 같이 정의하고 있습니다.
> 컨테이너란 : 애플리케이션의 표준화된 이식 가능한 패키징
그런데 왜 머신러닝에서 컨테이너가 필요할까요? 머신러닝 모델들은 운영체제나 Python 실행 환경, 패키지 버전 등에 따라 다르게 동작할 수 있습니다.
이를 방지하기 위해서 머신러닝에 사용된 소스 코드와 함께 종속적인 실행 환경 전체를 **하나로 묶어서(패키징해서)** 공유하고 실행하는 데 활용할 수 있는 기술이 컨테이너라이제이션(Containerization) 기술입니다.
이렇게 패키징된 형태를 컨테이너 이미지라고 부르며, 컨테이너 이미지를 공유함으로써 사용자들은 어떤 시스템에서든 같은 실행 결과를 보장할 수 있게 됩니다.
즉, 단순히 Jupyter Notebook 파일이나, 모델의 소스 코드와 requirements.txt 파일을 공유하는 것이 아닌, 모든 실행 환경이 담긴 컨테이너 이미지를 공유한다면 *"제 노트북에서는 잘 되는데요?"* 와 같은 상황을 피할 수 있습니다.
컨테이너를 처음 접하시는 분들이 흔히 하시는 오해 중 하나는 "**컨테이너 == 도커**"라고 받아들이는 것입니다.
도커는 컨테이너와 같은 의미를 지니는 개념이 아니라, 컨테이너를 띄우거나, 컨테이너 이미지를 만들고 공유하는 것과 같이 컨테이너를 더욱더 쉽고 유연하게 사용할 수 있는 기능을 제공해주는 도구입니다. 정리하자면 컨테이너는 가상화 기술이고, 도커는 가상화 기술의 구현체라고 말할 수 있습니다.
다만, 도커는 여러 컨테이너 가상화 도구 중에서 쉬운 사용성과 높은 효율성을 바탕으로 가장 빠르게 성장하여 대세가 되었기에 컨테이너하면 도커라는 이미지가 자동으로 떠오르게 되었습니다. 이렇게 컨테이너와 도커 생태계가 대세가 되기까지는 다양한 이유가 있지만, 기술적으로 자세한 이야기는 *모두의 MLOps*의 범위를 넘어서기 때문에 다루지는 않겠습니다.
컨테이너 혹은 도커를 처음 들어보시는 분들에게는 *모두의 MLOps*의 내용이 다소 어렵게 느껴질 수 있으므로, [생활코딩](https://opentutorials.org/course/4781), [subicura 님의 개인 블로그 글](https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html) 등의 자료를 먼저 살펴보는 것을 권장합니다.
## Container Orchestration System
그렇다면 컨테이너 오케스트레이션 시스템은 무엇일까요? **오케스트레이션**이라는 단어에서 추측해 볼 수 있듯이, 수많은 컨테이너가 있을 때 컨테이너들이 서로 조화롭게 구동될 수 있도록 지휘하는 시스템에 비유할 수 있습니다.
컨테이너 기반의 시스템에서 서비스는 컨테이너의 형태로 사용자들에게 제공됩니다. 이때 관리해야 할 컨테이너의 수가 적다면 운영 담당자 한 명이서도 충분히 모든 상황에 대응할 수 있습니다.
하지만, 수백 개 이상의 컨테이너가 수 십 대 이상의 클러스터에서 구동되고 있고 장애를 일으키지 않고 항상 정상 동작해야 한다면, 모든 서비스의 정상 동작 여부를 담당자 한 명이 파악하고 이슈에 대응하는 것은 불가능에 가깝습니다.
예를 들면, 모든 서비스가 정상적으로 동작하고 있는지를 계속해서 모니터링(Monitoring)해야 합니다.
만약, 특정 서비스가 장애를 일으켰다면 여러 컨테이너의 로그를 확인해가며 문제를 파악해야 합니다.
또한, 특정 클러스터나 특정 컨테이너에 작업이 몰리지 않도록 스케줄링(Scheduling)하고 로드 밸런싱(Load Balancing)하며, 스케일링(Scaling)하는 등의 수많은 작업을 담당해야 합니다.
이렇게 수많은 컨테이너의 상태를 지속해서 관리하고 운영하는 과정을 조금이나마 쉽게, 자동으로 할 수 있는 기능을 제공해주는 소프트웨어가 바로 컨테이너 오케스트레이션 시스템입니다.
머신러닝에서는 어떻게 쓰일 수 있을까요?
예를 들어서 GPU가 있어야 하는 딥러닝 학습 코드가 패키징된 컨테이너는 사용 가능한 GPU가 있는 클러스터에서 수행하고, 많은 메모리를 필요로 하는 데이터 전처리 코드가 패키징된 컨테이너는 메모리의 여유가 많은 클러스터에서 수행하고, 학습 중에 클러스터에 문제가 생기면 자동으로 같은 컨테이너를 다른 클러스터로 이동시키고 다시 학습을 진행하는 등의 작업을 사람이 일일이 수행하지 않고, 자동으로 관리하는 시스템을 개발한 뒤 맡기는 것입니다.
집필을 하는 2022년을 기준으로 쿠버네티스는 컨테이너 오케스트레이션 시스템의 사실상의 표준(De facto standard)입니다.
CNCF에서 2018년 발표한 [Survey](https://www.cncf.io/blog/2018/08/29/cncf-survey-use-of-cloud-native-technologies-in-production-has-grown-over-200-percent/) 에 따르면 다음 그림과 같이 이미 두각을 나타내고 있었으며, 2019년 발표한 [Survey](https://www.cncf.io/wp-content/uploads/2020/08/CNCF_Survey_Report.pdf)에 따르면 그중 78%가 상용 수준(Production Level)에서 사용하고 있다는 것을 알 수 있습니다.

쿠버네티스 생태계가 이처럼 커지게 된 이유에는 여러 가지 이유가 있습니다. 하지만 도커와 마찬가지로 쿠버네티스 역시 머신러닝 기반의 서비스에서만 사용하는 기술이 아니기에, 자세히 다루기에는 상당히 많은 양의 기술적인 내용을 다루어야 하므로 이번 *모두의 MLOps*에서는 자세한 내용은 생략할 예정입니다.
다만, *모두의 MLOps*에서 앞으로 다룰 내용은 도커와 쿠버네티스에 대한 내용을 어느 정도 알고 계신 분들을 대상으로 작성하였습니다. 따라서 쿠버네티스에 대해 익숙하지 않으신 분들은 다음 [쿠버네티스 공식 문서](https://kubernetes.io/ko/docs/concepts/overview/what-is-kubernetes/), [subicura 님의 개인 블로그 글](https://subicura.com/k8s/) 등의 쉽고 자세한 자료들을 먼저 참고해주시는 것을 권장합니다.
================================================
FILE: docs/kubeflow/_category_.json
================================================
{
"label": "Kubeflow",
"position": 6,
"link": {
"type": "generated-index"
}
}
================================================
FILE: docs/kubeflow/advanced-component.md
================================================
---
title : "8. Component - InputPath/OutputPath"
description: ""
sidebar_position: 8
contributors: ["Jongseob Jeon", "SeungTae Kim"]
---
## Complex Outputs
이번 페이지에서는 [Kubeflow Concepts](../kubeflow/kubeflow-concepts.md#component-contents) 예시로 나왔던 코드를 컴포넌트로 작성해 보겠습니다.
## Component Contents
아래 코드는 [Kubeflow Concepts](../kubeflow/kubeflow-concepts.md#component-contents)에서 사용했던 컴포넌트 콘텐츠입니다.
```python
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
```
## Component Wrapper
### Define a standalone Python function
컴포넌트 래퍼에 필요한 Config들과 함께 작성하면 다음과 같이 됩니다.
```python
def train_from_csv(
train_data_path: str,
train_target_path: str,
model_path: str,
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
```
[Basic Usage Component](../kubeflow/basic-component)에서 설명할 때 입력과 출력에 대한 타입 힌트를 적어야 한다고 설명 했었습니다. 그런데 만약 json에서 사용할 수 있는 기본 타입이 아닌 dataframe, model와 같이 복잡한 객체들은 어떻게 할까요?
파이썬에서 함수간에 값을 전달할 때, 객체를 반환해도 그 값이 호스트의 메모리에 저장되어 있으므로 다음 함수에서도 같은 객체를 사용할 수 있습니다. 하지만 kubeflow에서 컴포넌트들은 각각 컨테이너 위에서 서로 독립적으로 실행됩니다. 즉, 같은 메모리를 공유하고 있지 않기 때문에, 보통의 파이썬 함수에서 사용하는 방식과 같이 객체를 전달할 수 없습니다. 컴포넌트 간에 넘겨 줄 수 있는 정보는 `json` 으로만 가능합니다. 따라서 Model이나 DataFrame과 같이 json 형식으로 변환할 수 없는 타입의 객체는 다른 방법을 통해야 합니다.
Kubeflow에서는 이를 해결하기 위해 json-serializable 하지 않은 타입의 객체는 메모리 대신 파일에 데이터를 저장한 뒤, 그 파일을 이용해 정보를 전달합니다. 저장된 파일의 경로는 str이기 때문에 컴포넌트 간에 전달할 수 있기 때문입니다. 그런데 kubeflow에서는 minio를 이용해 파일을 저장하는데 유저는 실행을 하기 전에는 각 파일의 경로를 알 수 없습니다. 이를 위해서 kubeflow에서는 입력과 출력의 경로와 관련된 매직을 제공하는데 바로 `InputPath`와 `OutputPath` 입니다.
`InputPath`는 단어 그대로 입력 경로를 `OutputPath` 는 단어 그대로 출력 경로를 의미합니다.
예를 들어서 데이터를 생성하고 반환하는 컴포넌트에서는 `data_path: OutputPath()`를 argument로 만듭니다.
그리고 데이터를 받는 컴포넌트에서는 `data_path: InputPath()`을 argument로 생성합니다.
이렇게 만든 후 파이프라인에서 서로 연결을 하면 kubeflow에서 필요한 경로를 자동으로 생성후 입력해 주기 때문에 더 이상 유저는 경로를 신경쓰지 않고 컴포넌트간의 관계만 신경쓰면 됩니다.
이제 이 내용을 바탕으로 다시 컴포넌트 래퍼를 작성하면 다음과 같이 됩니다.
```python
from kfp.components import InputPath, OutputPath
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
```
InputPath나 OutputPath는 string을 입력할 수 있습니다. 이 string은 입력 또는 출력하려고 하는 파일의 포맷입니다.
그렇다고 꼭 이 포맷으로 파일 형태로 저장이 강제되는 것은 아닙니다.
다만 파이프라인을 컴파일할 때 최소한의 타입 체크를 위한 도우미 역할을 합니다.
만약 파일 포맷이 고정되지 않는다면 입력하지 않으면 됩니다 (타입 힌트 에서 `Any` 와 같은 역할을 합니다).
### Convert to Kubeflow Format
작성한 컴포넌트를 kubeflow에서 사용할 수 있는 포맷으로 변환합니다.
```python
from kfp.components import InputPath, OutputPath, create_component_from_func
@create_component_from_func
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
```
## Rule to use InputPath/OutputPath
InputPath나 OutputPath argument는 파이프라인으로 작성할 때 지켜야하는 규칙이 있습니다.
### Load Data Component
위에서 작성한 컴포넌트를 실행하기 위해서는 데이터가 필요하므로 데이터를 생성하는 컴포넌트를 작성합니다.
```python
from functools import partial
from kfp.components import InputPath, OutputPath, create_component_from_func
@create_component_from_func
def load_iris_data(
data_path: OutputPath("csv"),
target_path: OutputPath("csv"),
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
```
### Write Pipeline
이제 파이프라인을 작성해 보도록 하겠습니다.
```python
from kfp.dsl import pipeline
@pipeline(name="complex_pipeline")
def complex_pipeline(kernel: str):
iris_data = load_iris_data()
model = train_from_csv(
train_data=iris_data.outputs["data"],
train_target=iris_data.outputs["target"],
kernel=kernel,
)
```
한 가지 이상한 점을 확인하셨나요?
바로 입력과 출력에서 받는 argument중 경로와 관련된 것들에 `_path` 접미사가 모두 사라졌습니다.
`iris_data.outputs["data_path"]` 가 아닌 `iris_data.outputs["data"]` 으로 접근하는 것을 확인할 수 있습니다.
이는 kubeflow에서 정한 법칙으로 `InputPath` 와 `OutputPath` 으로 생성된 경로들은 파이프라인에서 접근할 때는 `_path` 접미사를 생략하여 접근합니다.
다만 방금 작성한 파이프라인을 업로드할 경우 실행이 되지 않습니다.
이유는 다음 페이지에서 설명합니다.
================================================
FILE: docs/kubeflow/advanced-environment.md
================================================
---
title : "9. Component - Environment"
description: ""
sidebar_position: 9
contributors: ["Jongseob Jeon"]
---
## Component Environment
앞서 [8. Component - InputPath/OutputPath](../kubeflow/advanced-component.md)에서 작성한 파이프라인을 실행하면 실패하게 됩니다. 왜 실패하는지 알아보고 정상적으로 실행될 수 있도록 수정합니다.
### Convert to Kubeflow Format
[앞에서 작성한 컴포넌트](../kubeflow/advanced-component.md#convert-to-kubeflow-format)를 yaml파일로 변환하도록 하겠습니다.
```python
from kfp.components import InputPath, OutputPath, create_component_from_func
@create_component_from_func
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
if __name__ == "__main__":
train_from_csv.component_spec.save("train_from_csv.yaml")
```
위의 스크립트를 실행하면 다음과 같은 `train_from_csv.yaml` 파일을 얻을 수 있습니다.
```bash
name: Train from csv
inputs:
- {name: train_data, type: csv}
- {name: train_target, type: csv}
- {name: model, type: dill}
- {name: kernel, type: String}
implementation:
container:
image: python:3.7
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def train_from_csv(
train_data_path,
train_target_path,
model_path,
kernel,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
import argparse
_parser = argparse.ArgumentParser(prog='Train from csv', description='')
_parser.add_argument("--train-data", dest="train_data_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--train-target", dest="train_target_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--model", dest="model_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--kernel", dest="kernel", type=str, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = train_from_csv(**_parsed_args)
args:
- --train-data
- {inputPath: train_data}
- --train-target
- {inputPath: train_target}
- --model
- {inputPath: model}
- --kernel
- {inputValue: kernel}
```
앞서 [Basic Usage Component](../kubeflow/basic-component.md#convert-to-kubeflow-format)에서 설명한 내용에 따르면 이 컴포넌트는 다음과 같이 실행됩니다.
1. `docker pull python:3.7`
2. run `command`
하지만 위에서 생성된 컴포넌트를 실행하면 오류가 발생하게 됩니다.
그 이유는 컴포넌트 래퍼가 실행되는 방식에 있습니다.
Kubeflow는 쿠버네티스를 이용하기 때문에 컴포넌트 래퍼는 각각 독립된 컨테이너 위에서 컴포넌트 콘텐츠를 실행합니다.
자세히 보면 생성된 만든 `train_from_csv.yaml` 에서 정해진 이미지는 `image: python:3.7` 입니다.
이제 어떤 이유 때문에 실행이 안 되는지 눈치채신 분들도 있을 것입니다.
`python:3.7` 이미지에는 우리가 사용하고자 하는 `dill`, `pandas`, `sklearn` 이 설치되어 있지 않습니다.
그러므로 실행할 때 해당 패키지가 존재하지 않는다는 에러와 함께 실행이 안 됩니다.
그럼 어떻게 패키지를 추가할 수 있을까요?
## 패키지 추가 방법
Kubeflow를 변환하는 과정에서 두 가지 방법을 통해 패키지를 추가할 수 있습니다.
1. `base_image` 사용
2. `package_to_install` 사용
컴포넌트를 컴파일할 때 사용했던 함수 `create_component_from_func` 가 어떤 argument들을 받을 수 있는지 확인해 보겠습니다.
```bash
def create_component_from_func(
func: Callable,
output_component_file: Optional[str] = None,
base_image: Optional[str] = None,
packages_to_install: List[str] = None,
annotations: Optional[Mapping[str, str]] = None,
):
```
- `func`: 컴포넌트로 만들 컴포넌트 래퍼 함수
- `base_image`: 컴포넌트 래퍼가 실행할 이미지
- `packages_to_install`: 컴포넌트에서 사용해서 추가로 설치해야 하는 패키지
### 1. base_image
컴포넌트가 실행되는 순서를 좀 더 자세히 들여다보면 다음과 같습니다.
1. `docker pull base_image`
2. `pip install packages_to_install`
3. run `command`
만약 컴포넌트가 사용하는 base_image에 패키지들이 전부 설치되어 있다면 추가적인 패키지 설치 없이 바로 사용할 수 있습니다.
예를 들어, 이번 페이지에서는 다음과 같은 Dockerfile을 작성하겠습니다.
```dockerfile
FROM python:3.7
RUN pip install dill pandas scikit-learn
```
위의 Dockerfile을 이용해 이미지를 빌드해 보겠습니다. 실습에서 사용해볼 도커 허브는 ghcr입니다.
각자 환경에 맞추어서 도커 허브를 선택 후 업로드하면 됩니다.
```bash
docker build . -f Dockerfile -t ghcr.io/mlops-for-all/base-image
docker push ghcr.io/mlops-for-all/base-image
```
이제 base_image를 입력해 보겠습니다.
```python
from functools import partial
from kfp.components import InputPath, OutputPath, create_component_from_func
@partial(
create_component_from_func,
base_image="ghcr.io/mlops-for-all/base-image:latest",
)
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
if __name__ == "__main__":
train_from_csv.component_spec.save("train_from_csv.yaml")
```
이제 생성된 컴포넌트를 컴파일하면 다음과 같이 나옵니다.
```bash
name: Train from csv
inputs:
- {name: train_data, type: csv}
- {name: train_target, type: csv}
- {name: kernel, type: String}
outputs:
- {name: model, type: dill}
implementation:
container:
image: ghcr.io/mlops-for-all/base-image:latest
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def _make_parent_dirs_and_return_path(file_path: str):
import os
os.makedirs(os.path.dirname(file_path), exist_ok=True)
return file_path
def train_from_csv(
train_data_path,
train_target_path,
model_path,
kernel,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
import argparse
_parser = argparse.ArgumentParser(prog='Train from csv', description='')
_parser.add_argument("--train-data", dest="train_data_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--train-target", dest="train_target_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--kernel", dest="kernel", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--model", dest="model_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = train_from_csv(**_parsed_args)
args:
- --train-data
- {inputPath: train_data}
- --train-target
- {inputPath: train_target}
- --kernel
- {inputValue: kernel}
- --model
- {outputPath: model}
```
base_image가 우리가 설정한 값으로 바뀐 것을 확인할 수 있습니다.
### 2. packages_to_install
하지만 패키지가 추가될 때마다 docker 이미지를 계속해서 새로 생성하는 작업은 많은 시간이 소요됩니다.
이 때, `packages_to_install` argument 를 사용하면 패키지를 컨테이너에 쉽게 추가할 수 있습니다.
```python
from functools import partial
from kfp.components import InputPath, OutputPath, create_component_from_func
@partial(
create_component_from_func,
packages_to_install=["dill==0.3.4", "pandas==1.3.4", "scikit-learn==1.0.1"],
)
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
if __name__ == "__main__":
train_from_csv.component_spec.save("train_from_csv.yaml")
```
스크립트를 실행하면 다음과 같은 `train_from_csv.yaml` 파일이 생성됩니다.
```bash
name: Train from csv
inputs:
- {name: train_data, type: csv}
- {name: train_target, type: csv}
- {name: kernel, type: String}
outputs:
- {name: model, type: dill}
implementation:
container:
image: python:3.7
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'dill==0.3.4' 'pandas==1.3.4' 'scikit-learn==1.0.1' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'dill==0.3.4' 'pandas==1.3.4'
'scikit-learn==1.0.1' --user) && "$0" "$@"
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def _make_parent_dirs_and_return_path(file_path: str):
import os
os.makedirs(os.path.dirname(file_path), exist_ok=True)
return file_path
def train_from_csv(
train_data_path,
train_target_path,
model_path,
kernel,
):
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
import argparse
_parser = argparse.ArgumentParser(prog='Train from csv', description='')
_parser.add_argument("--train-data", dest="train_data_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--train-target", dest="train_target_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--kernel", dest="kernel", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--model", dest="model_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = train_from_csv(**_parsed_args)
args:
- --train-data
- {inputPath: train_data}
- --train-target
- {inputPath: train_target}
- --kernel
- {inputValue: kernel}
- --model
- {outputPath: model}
```
위에 작성한 컴포넌트가 실행되는 순서를 좀 더 자세히 들여다보면 다음과 같습니다.
1. `docker pull python:3.7`
2. `pip install dill==0.3.4 pandas==1.3.4 scikit-learn==1.0.1`
3. run `command`
생성된 yaml 파일을 자세히 보면, 다음과 같은 줄이 자동으로 추가되어 필요한 패키지가 설치되기 때문에 오류 없이 정상적으로 실행됩니다.
```bash
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'dill==0.3.4' 'pandas==1.3.4' 'scikit-learn==1.0.1' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'dill==0.3.4' 'pandas==1.3.4'
'scikit-learn==1.0.1' --user) && "$0" "$@"
```
================================================
FILE: docs/kubeflow/advanced-mlflow.md
================================================
---
title : "12. Component - MLFlow"
description: ""
sidebar_position: 12
date: 2021-12-13
lastmod: 2021-12-20
contributors: ["Jongseob Jeon", "SeungTae Kim"]
---
## MLFlow Component
[Advanced Usage Component](../kubeflow/advanced-component.md) 에서 학습한 모델이 API Deployment까지 이어지기 위해서는 MLFlow에 모델을 저장해야 합니다.
이번 페이지에서는 MLFlow에 모델을 저장할 수 있는 컴포넌트를 작성하는 과정을 설명합니다.
## MLFlow in Local
MLFlow에서 모델을 저장하고 서빙에서 사용하기 위해서는 다음의 항목들이 필요합니다.
- model
- signature
- input_example
- conda_env
파이썬 코드를 통해서 MLFLow에 모델을 저장하는 과정에 대해서 알아보겠습니다.
### 1. 모델 학습
아래 과정은 iris 데이터를 이용해 SVC 모델을 학습하는 과정입니다.
```python
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.svm import SVC
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
clf = SVC(kernel="rbf")
clf.fit(data, target)
```
### 2. MLFLow Infos
mlflow에 필요한 정보들을 만드는 과정입니다.
```python
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
input_example = data.sample(1)
signature = infer_signature(data, clf.predict(data))
conda_env = _mlflow_conda_env(additional_pip_deps=["dill", "pandas", "scikit-learn"])
```
각 변수의 내용을 확인하면 다음과 같습니다.
- `input_example`
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) |
| --- | --- | --- | --- |
| 6.5 | 6.7 | 3.1 | 4.4 |
- `signature`
```python
inputs:
['sepal length (cm)': double, 'sepal width (cm)': double, 'petal length (cm)': double, 'petal width (cm)': double]
outputs:
[Tensor('int64', (-1,))]
```
- `conda_env`
```python
{'name': 'mlflow-env',
'channels': ['conda-forge'],
'dependencies': ['python=3.8.10',
'pip',
{'pip': ['mlflow', 'dill', 'pandas', 'scikit-learn']}]}
```
### 3. Save MLFLow Infos
다음으로 학습한 정보들과 모델을 저장합니다.
학습한 모델이 sklearn 패키지를 이용하기 때문에 `mlflow.sklearn` 을 이용하면 쉽게 모델을 저장할 수 있습니다.
```python
from mlflow.sklearn import save_model
save_model(
sk_model=clf,
path="svc",
serialization_format="cloudpickle",
conda_env=conda_env,
signature=signature,
input_example=input_example,
)
```
로컬에서 작업하면 다음과 같은 svc 폴더가 생기며 아래와 같은 파일들이 생성됩니다.
```bash
ls svc
```
위의 명령어를 실행하면 다음의 출력값을 확인할 수 있습니다.
```bash
MLmodel conda.yaml input_example.json model.pkl requirements.txt
```
각 파일을 확인하면 다음과 같습니다.
- MLmodel
```bash
flavors:
python_function:
env: conda.yaml
loader_module: mlflow.sklearn
model_path: model.pkl
python_version: 3.8.10
sklearn:
pickled_model: model.pkl
serialization_format: cloudpickle
sklearn_version: 1.0.1
saved_input_example_info:
artifact_path: input_example.json
pandas_orient: split
type: dataframe
signature:
inputs: '[{"name": "sepal length (cm)", "type": "double"}, {"name": "sepal width
(cm)", "type": "double"}, {"name": "petal length (cm)", "type": "double"}, {"name":
"petal width (cm)", "type": "double"}]'
outputs: '[{"type": "tensor", "tensor-spec": {"dtype": "int64", "shape": [-1]}}]'
utc_time_created: '2021-12-06 06:52:30.612810'
```
- conda.yaml
```bash
channels:
- conda-forge
dependencies:
- python=3.8.10
- pip
- pip:
- mlflow
- dill
- pandas
- scikit-learn
name: mlflow-env
```
- input_example.json
```bash
{
"columns":
[
"sepal length (cm)",
"sepal width (cm)",
"petal length (cm)",
"petal width (cm)"
],
"data":
[
[6.7, 3.1, 4.4, 1.4]
]
}
```
- requirements.txt
```bash
mlflow
dill
pandas
scikit-learn
```
- model.pkl
## MLFlow on Server
이제 저장된 모델을 mlflow 서버에 올리는 작업을 해보겠습니다.
```python
import mlflow
with mlflow.start_run():
mlflow.log_artifact("svc/")
```
저장하고 `mlruns` 가 생성된 경로에서 `mlflow ui` 명령어를 이용해 mlflow 서버와 대시보드를 띄웁니다.
mlflow 대시보드에 접속하여 생성된 run을 클릭하면 다음과 같이 보입니다.

(해당 화면은 mlflow 버전에 따라 다를 수 있습니다.)
## MLFlow Component
이제 Kubeflow에서 재사용할 수 있는 컴포넌트를 작성해 보겠습니다.
재사용할 수 있는 컴포넌트를 작성하는 방법은 크게 3가지가 있습니다.
1. 모델을 학습하는 컴포넌트에서 필요한 환경을 저장 후 MLFlow 컴포넌트는 업로드만 담당

2. 학습된 모델과 데이터를 MLFlow 컴포넌트에 전달 후 컴포넌트에서 저장과 업로드 담당

3. 모델을 학습하는 컴포넌트에서 저장과 업로드를 담당

저희는 이 중 1번의 접근 방법을 통해 모델을 관리하려고 합니다.
이유는 MLFlow 모델을 업로드하는 코드는 바뀌지 않기 때문에 매번 3번처럼 컴포넌트 작성마다 작성할 필요는 없기 때문입니다.
컴포넌트를 재활용하는 방법은 1번과 2번의 방법으로 가능합니다.
다만 2번의 경우 모델이 학습된 이미지와 패키지들을 전달해야 하므로 결국 컴포넌트에 대한 추가 정보를 전달해야 합니다.
1번의 방법으로 진행하기 위해서는 학습하는 컴포넌트 또한 변경되어야 합니다.
모델을 저장하는데 필요한 환경들을 저장해주는 코드가 추가되어야 합니다.
```python
from functools import partial
from kfp.components import InputPath, OutputPath, create_component_from_func
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow"],
)
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
input_example_path: OutputPath("dill"),
signature_path: OutputPath("dill"),
conda_env_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
input_example = train_data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(train_data, clf.predict(train_data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["dill", "pandas", "scikit-learn"]
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
```
그리고 MLFlow에 업로드하는 컴포넌트를 작성합니다.
이 때 업로드되는 MLflow의 endpoint를 우리가 설치한 [mlflow service](../setup-components/install-components-mlflow.md) 로 이어지게 설정해주어야 합니다.
이 때 S3 Endpoint의 주소는 MLflow Server 설치 당시 설치한 minio의 [쿠버네티스 서비스 DNS 네임을 활용](https://kubernetes.io/ko/docs/concepts/services-networking/dns-pod-service/)합니다. 해당 service 는 kubeflow namespace에서 minio-service라는 이름으로 생성되었으므로, `http://minio-service.kubeflow.svc:9000` 로 설정합니다.
이와 비슷하게 tracking_uri의 주소는 mlflow server의 쿠버네티스 서비스 DNS 네임을 활용하여, `http://mlflow-server-service.mlflow-system.svc:5000` 로 설정합니다.
```python
from functools import partial
from kfp.components import InputPath, create_component_from_func
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow", "boto3"],
)
def upload_sklearn_model_to_mlflow(
model_name: str,
model_path: InputPath("dill"),
input_example_path: InputPath("dill"),
signature_path: InputPath("dill"),
conda_env_path: InputPath("dill"),
):
import os
import dill
from mlflow.sklearn import save_model
from mlflow.tracking.client import MlflowClient
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://minio-service.kubeflow.svc:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minio"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123"
client = MlflowClient("http://mlflow-server-service.mlflow-system.svc:5000")
with open(model_path, mode="rb") as file_reader:
clf = dill.load(file_reader)
with open(input_example_path, "rb") as file_reader:
input_example = dill.load(file_reader)
with open(signature_path, "rb") as file_reader:
signature = dill.load(file_reader)
with open(conda_env_path, "rb") as file_reader:
conda_env = dill.load(file_reader)
save_model(
sk_model=clf,
path=model_name,
serialization_format="cloudpickle",
conda_env=conda_env,
signature=signature,
input_example=input_example,
)
run = client.create_run(experiment_id="0")
client.log_artifact(run.info.run_id, model_name)
```
## MLFlow Pipeline
이제 작성한 컴포넌트들을 연결해서 파이프라인으로 만들어 보겠습니다.
### Data Component
모델을 학습할 때 쓸 데이터는 sklearn의 iris 입니다.
데이터를 생성하는 컴포넌트를 작성합니다.
```python
from functools import partial
from kfp.components import InputPath, OutputPath, create_component_from_func
@partial(
create_component_from_func,
packages_to_install=["pandas", "scikit-learn"],
)
def load_iris_data(
data_path: OutputPath("csv"),
target_path: OutputPath("csv"),
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
```
### Pipeline
파이프라인 코드는 다음과 같이 작성할 수 있습니다.
```python
from kfp.dsl import pipeline
@pipeline(name="mlflow_pipeline")
def mlflow_pipeline(kernel: str, model_name: str):
iris_data = load_iris_data()
model = train_from_csv(
train_data=iris_data.outputs["data"],
train_target=iris_data.outputs["target"],
kernel=kernel,
)
_ = upload_sklearn_model_to_mlflow(
model_name=model_name,
model=model.outputs["model"],
input_example=model.outputs["input_example"],
signature=model.outputs["signature"],
conda_env=model.outputs["conda_env"],
)
```
### Run
위에서 작성된 컴포넌트와 파이프라인을 하나의 파이썬 파일에 정리하면 다음과 같습니다.
```python
from functools import partial
import kfp
from kfp.components import InputPath, OutputPath, create_component_from_func
from kfp.dsl import pipeline
@partial(
create_component_from_func,
packages_to_install=["pandas", "scikit-learn"],
)
def load_iris_data(
data_path: OutputPath("csv"),
target_path: OutputPath("csv"),
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow"],
)
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
input_example_path: OutputPath("dill"),
signature_path: OutputPath("dill"),
conda_env_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
input_example = train_data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(train_data, clf.predict(train_data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["dill", "pandas", "scikit-learn"]
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow", "boto3"],
)
def upload_sklearn_model_to_mlflow(
model_name: str,
model_path: InputPath("dill"),
input_example_path: InputPath("dill"),
signature_path: InputPath("dill"),
conda_env_path: InputPath("dill"),
):
import os
import dill
from mlflow.sklearn import save_model
from mlflow.tracking.client import MlflowClient
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://minio-service.kubeflow.svc:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minio"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123"
client = MlflowClient("http://mlflow-server-service.mlflow-system.svc:5000")
with open(model_path, mode="rb") as file_reader:
clf = dill.load(file_reader)
with open(input_example_path, "rb") as file_reader:
input_example = dill.load(file_reader)
with open(signature_path, "rb") as file_reader:
signature = dill.load(file_reader)
with open(conda_env_path, "rb") as file_reader:
conda_env = dill.load(file_reader)
save_model(
sk_model=clf,
path=model_name,
serialization_format="cloudpickle",
conda_env=conda_env,
signature=signature,
input_example=input_example,
)
run = client.create_run(experiment_id="0")
client.log_artifact(run.info.run_id, model_name)
@pipeline(name="mlflow_pipeline")
def mlflow_pipeline(kernel: str, model_name: str):
iris_data = load_iris_data()
model = train_from_csv(
train_data=iris_data.outputs["data"],
train_target=iris_data.outputs["target"],
kernel=kernel,
)
_ = upload_sklearn_model_to_mlflow(
model_name=model_name,
model=model.outputs["model"],
input_example=model.outputs["input_example"],
signature=model.outputs["signature"],
conda_env=model.outputs["conda_env"],
)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(mlflow_pipeline, "mlflow_pipeline.yaml")
```
<p>
<details>
<summary>mlflow_pipeline.yaml</summary>
```bash
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: mlflow-pipeline-
annotations: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.10, pipelines.kubeflow.org/pipeline_compilation_time: '2022-01-19T14:14:11.999807',
pipelines.kubeflow.org/pipeline_spec: '{"inputs": [{"name": "kernel", "type":
"String"}, {"name": "model_name", "type": "String"}], "name": "mlflow_pipeline"}'}
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.10}
spec:
entrypoint: mlflow-pipeline
templates:
- name: load-iris-data
container:
args: [--data, /tmp/outputs/data/data, --target, /tmp/outputs/target/data]
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'pandas' 'scikit-learn' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip
install --quiet --no-warn-script-location 'pandas' 'scikit-learn' --user)
&& "$0" "$@"
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def _make_parent_dirs_and_return_path(file_path: str):
import os
os.makedirs(os.path.dirname(file_path), exist_ok=True)
return file_path
def load_iris_data(
data_path,
target_path,
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
import argparse
_parser = argparse.ArgumentParser(prog='Load iris data', description='')
_parser.add_argument("--data", dest="data_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--target", dest="target_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = load_iris_data(**_parsed_args)
image: python:3.7
outputs:
artifacts:
- {name: load-iris-data-data, path: /tmp/outputs/data/data}
- {name: load-iris-data-target, path: /tmp/outputs/target/data}
metadata:
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.10
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--data", {"outputPath": "data"}, "--target", {"outputPath": "target"}],
"command": ["sh", "-c", "(PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip
install --quiet --no-warn-script-location ''pandas'' ''scikit-learn'' ||
PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
''pandas'' ''scikit-learn'' --user) && \"$0\" \"$@\"", "sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def _make_parent_dirs_and_return_path(file_path: str):\n import os\n os.makedirs(os.path.dirname(file_path),
exist_ok=True)\n return file_path\n\ndef load_iris_data(\n data_path,\n target_path,\n):\n import
pandas as pd\n from sklearn.datasets import load_iris\n\n iris = load_iris()\n\n data
= pd.DataFrame(iris[\"data\"], columns=iris[\"feature_names\"])\n target
= pd.DataFrame(iris[\"target\"], columns=[\"target\"])\n\n data.to_csv(data_path,
index=False)\n target.to_csv(target_path, index=False)\n\nimport argparse\n_parser
= argparse.ArgumentParser(prog=''Load iris data'', description='''')\n_parser.add_argument(\"--data\",
dest=\"data_path\", type=_make_parent_dirs_and_return_path, required=True,
default=argparse.SUPPRESS)\n_parser.add_argument(\"--target\", dest=\"target_path\",
type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)\n_parsed_args
= vars(_parser.parse_args())\n\n_outputs = load_iris_data(**_parsed_args)\n"],
"image": "python:3.7"}}, "name": "Load iris data", "outputs": [{"name":
"data", "type": "csv"}, {"name": "target", "type": "csv"}]}', pipelines.kubeflow.org/component_ref: '{}'}
- name: mlflow-pipeline
inputs:
parameters:
- {name: kernel}
- {name: model_name}
dag:
tasks:
- {name: load-iris-data, template: load-iris-data}
- name: train-from-csv
template: train-from-csv
dependencies: [load-iris-data]
arguments:
parameters:
- {name: kernel, value: '{{inputs.parameters.kernel}}'}
artifacts:
- {name: load-iris-data-data, from: '{{tasks.load-iris-data.outputs.artifacts.load-iris-data-data}}'}
- {name: load-iris-data-target, from: '{{tasks.load-iris-data.outputs.artifacts.load-iris-data-target}}'}
- name: upload-sklearn-model-to-mlflow
template: upload-sklearn-model-to-mlflow
dependencies: [train-from-csv]
arguments:
parameters:
- {name: model_name, value: '{{inputs.parameters.model_name}}'}
artifacts:
- {name: train-from-csv-conda_env, from: '{{tasks.train-from-csv.outputs.artifacts.train-from-csv-conda_env}}'}
- {name: train-from-csv-input_example, from: '{{tasks.train-from-csv.outputs.artifacts.train-from-csv-input_example}}'}
- {name: train-from-csv-model, from: '{{tasks.train-from-csv.outputs.artifacts.train-from-csv-model}}'}
- {name: train-from-csv-signature, from: '{{tasks.train-from-csv.outputs.artifacts.train-from-csv-signature}}'}
- name: train-from-csv
container:
args: [--train-data, /tmp/inputs/train_data/data, --train-target, /tmp/inputs/train_target/data,
--kernel, '{{inputs.parameters.kernel}}', --model, /tmp/outputs/model/data,
--input-example, /tmp/outputs/input_example/data, --signature, /tmp/outputs/signature/data,
--conda-env, /tmp/outputs/conda_env/data]
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'dill' 'pandas' 'scikit-learn' 'mlflow' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'dill' 'pandas' 'scikit-learn'
'mlflow' --user) && "$0" "$@"
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def _make_parent_dirs_and_return_path(file_path: str):
import os
os.makedirs(os.path.dirname(file_path), exist_ok=True)
return file_path
def train_from_csv(
train_data_path,
train_target_path,
model_path,
input_example_path,
signature_path,
conda_env_path,
kernel,
):
import dill
import pandas as pd
from sklearn.svm import SVC
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
input_example = train_data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(train_data, clf.predict(train_data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["dill", "pandas", "scikit-learn"]
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
import argparse
_parser = argparse.ArgumentParser(prog='Train from csv', description='')
_parser.add_argument("--train-data", dest="train_data_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--train-target", dest="train_target_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--kernel", dest="kernel", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--model", dest="model_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--input-example", dest="input_example_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--signature", dest="signature_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--conda-env", dest="conda_env_path", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = train_from_csv(**_parsed_args)
image: python:3.7
inputs:
parameters:
- {name: kernel}
artifacts:
- {name: load-iris-data-data, path: /tmp/inputs/train_data/data}
- {name: load-iris-data-target, path: /tmp/inputs/train_target/data}
outputs:
artifacts:
- {name: train-from-csv-conda_env, path: /tmp/outputs/conda_env/data}
- {name: train-from-csv-input_example, path: /tmp/outputs/input_example/data}
- {name: train-from-csv-model, path: /tmp/outputs/model/data}
- {name: train-from-csv-signature, path: /tmp/outputs/signature/data}
metadata:
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.10
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--train-data", {"inputPath": "train_data"}, "--train-target",
{"inputPath": "train_target"}, "--kernel", {"inputValue": "kernel"}, "--model",
{"outputPath": "model"}, "--input-example", {"outputPath": "input_example"},
"--signature", {"outputPath": "signature"}, "--conda-env", {"outputPath":
"conda_env"}], "command": ["sh", "-c", "(PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location ''dill'' ''pandas''
''scikit-learn'' ''mlflow'' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m
pip install --quiet --no-warn-script-location ''dill'' ''pandas'' ''scikit-learn''
''mlflow'' --user) && \"$0\" \"$@\"", "sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def _make_parent_dirs_and_return_path(file_path: str):\n import os\n os.makedirs(os.path.dirname(file_path),
exist_ok=True)\n return file_path\n\ndef train_from_csv(\n train_data_path,\n train_target_path,\n model_path,\n input_example_path,\n signature_path,\n conda_env_path,\n kernel,\n):\n import
dill\n import pandas as pd\n from sklearn.svm import SVC\n\n from
mlflow.models.signature import infer_signature\n from mlflow.utils.environment
import _mlflow_conda_env\n\n train_data = pd.read_csv(train_data_path)\n train_target
= pd.read_csv(train_target_path)\n\n clf = SVC(kernel=kernel)\n clf.fit(train_data,
train_target)\n\n with open(model_path, mode=\"wb\") as file_writer:\n dill.dump(clf,
file_writer)\n\n input_example = train_data.sample(1)\n with open(input_example_path,
\"wb\") as file_writer:\n dill.dump(input_example, file_writer)\n\n signature
= infer_signature(train_data, clf.predict(train_data))\n with open(signature_path,
\"wb\") as file_writer:\n dill.dump(signature, file_writer)\n\n conda_env
= _mlflow_conda_env(\n additional_pip_deps=[\"dill\", \"pandas\",
\"scikit-learn\"]\n )\n with open(conda_env_path, \"wb\") as file_writer:\n dill.dump(conda_env,
file_writer)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog=''Train
from csv'', description='''')\n_parser.add_argument(\"--train-data\", dest=\"train_data_path\",
type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--train-target\",
dest=\"train_target_path\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--kernel\",
dest=\"kernel\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--model\",
dest=\"model_path\", type=_make_parent_dirs_and_return_path, required=True,
default=argparse.SUPPRESS)\n_parser.add_argument(\"--input-example\", dest=\"input_example_path\",
type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--signature\",
dest=\"signature_path\", type=_make_parent_dirs_and_return_path, required=True,
default=argparse.SUPPRESS)\n_parser.add_argument(\"--conda-env\", dest=\"conda_env_path\",
type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)\n_parsed_args
= vars(_parser.parse_args())\n\n_outputs = train_from_csv(**_parsed_args)\n"],
"image": "python:3.7"}}, "inputs": [{"name": "train_data", "type": "csv"},
{"name": "train_target", "type": "csv"}, {"name": "kernel", "type": "String"}],
"name": "Train from csv", "outputs": [{"name": "model", "type": "dill"},
{"name": "input_example", "type": "dill"}, {"name": "signature", "type":
"dill"}, {"name": "conda_env", "type": "dill"}]}', pipelines.kubeflow.org/component_ref: '{}',
pipelines.kubeflow.org/arguments.parameters: '{"kernel": "{{inputs.parameters.kernel}}"}'}
- name: upload-sklearn-model-to-mlflow
container:
args: [--model-name, '{{inputs.parameters.model_name}}', --model, /tmp/inputs/model/data,
--input-example, /tmp/inputs/input_example/data, --signature, /tmp/inputs/signature/data,
--conda-env, /tmp/inputs/conda_env/data]
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'dill' 'pandas' 'scikit-learn' 'mlflow' 'boto3' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'dill' 'pandas' 'scikit-learn'
'mlflow' 'boto3' --user) && "$0" "$@"
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def upload_sklearn_model_to_mlflow(
model_name,
model_path,
input_example_path,
signature_path,
conda_env_path,
):
import os
import dill
from mlflow.sklearn import save_model
from mlflow.tracking.client import MlflowClient
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://minio-service.kubeflow.svc:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minio"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123"
client = MlflowClient("http://mlflow-server-service.mlflow-system.svc:5000")
with open(model_path, mode="rb") as file_reader:
clf = dill.load(file_reader)
with open(input_example_path, "rb") as file_reader:
input_example = dill.load(file_reader)
with open(signature_path, "rb") as file_reader:
signature = dill.load(file_reader)
with open(conda_env_path, "rb") as file_reader:
conda_env = dill.load(file_reader)
save_model(
sk_model=clf,
path=model_name,
serialization_format="cloudpickle",
conda_env=conda_env,
signature=signature,
input_example=input_example,
)
run = client.create_run(experiment_id="0")
client.log_artifact(run.info.run_id, model_name)
import argparse
_parser = argparse.ArgumentParser(prog='Upload sklearn model to mlflow', description='')
_parser.add_argument("--model-name", dest="model_name", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--model", dest="model_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--input-example", dest="input_example_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--signature", dest="signature_path", type=str, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--conda-env", dest="conda_env_path", type=str, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = upload_sklearn_model_to_mlflow(**_parsed_args)
image: python:3.7
inputs:
parameters:
- {name: model_name}
artifacts:
- {name: train-from-csv-conda_env, path: /tmp/inputs/conda_env/data}
- {name: train-from-csv-input_example, path: /tmp/inputs/input_example/data}
- {name: train-from-csv-model, path: /tmp/inputs/model/data}
- {name: train-from-csv-signature, path: /tmp/inputs/signature/data}
metadata:
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.10
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--model-name", {"inputValue": "model_name"}, "--model", {"inputPath":
"model"}, "--input-example", {"inputPath": "input_example"}, "--signature",
{"inputPath": "signature"}, "--conda-env", {"inputPath": "conda_env"}],
"command": ["sh", "-c", "(PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip
install --quiet --no-warn-script-location ''dill'' ''pandas'' ''scikit-learn''
''mlflow'' ''boto3'' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install
--quiet --no-warn-script-location ''dill'' ''pandas'' ''scikit-learn'' ''mlflow''
''boto3'' --user) && \"$0\" \"$@\"", "sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def upload_sklearn_model_to_mlflow(\n model_name,\n model_path,\n input_example_path,\n signature_path,\n conda_env_path,\n):\n import
os\n import dill\n from mlflow.sklearn import save_model\n\n from
mlflow.tracking.client import MlflowClient\n\n os.environ[\"MLFLOW_S3_ENDPOINT_URL\"]
= \"http://minio-service.kubeflow.svc:9000\"\n os.environ[\"AWS_ACCESS_KEY_ID\"]
= \"minio\"\n os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"minio123\"\n\n client
= MlflowClient(\"http://mlflow-server-service.mlflow-system.svc:5000\")\n\n with
open(model_path, mode=\"rb\") as file_reader:\n clf = dill.load(file_reader)\n\n with
open(input_example_path, \"rb\") as file_reader:\n input_example
= dill.load(file_reader)\n\n with open(signature_path, \"rb\") as file_reader:\n signature
= dill.load(file_reader)\n\n with open(conda_env_path, \"rb\") as file_reader:\n conda_env
= dill.load(file_reader)\n\n save_model(\n sk_model=clf,\n path=model_name,\n serialization_format=\"cloudpickle\",\n conda_env=conda_env,\n signature=signature,\n input_example=input_example,\n )\n run
= client.create_run(experiment_id=\"0\")\n client.log_artifact(run.info.run_id,
model_name)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog=''Upload
sklearn model to mlflow'', description='''')\n_parser.add_argument(\"--model-name\",
dest=\"model_name\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--model\",
dest=\"model_path\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--input-example\",
dest=\"input_example_path\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--signature\",
dest=\"signature_path\", type=str, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--conda-env\",
dest=\"conda_env_path\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args
= vars(_parser.parse_args())\n\n_outputs = upload_sklearn_model_to_mlflow(**_parsed_args)\n"],
"image": "python:3.7"}}, "inputs": [{"name": "model_name", "type": "String"},
{"name": "model", "type": "dill"}, {"name": "input_example", "type": "dill"},
{"name": "signature", "type": "dill"}, {"name": "conda_env", "type": "dill"}],
"name": "Upload sklearn model to mlflow"}', pipelines.kubeflow.org/component_ref: '{}',
pipelines.kubeflow.org/arguments.parameters: '{"model_name": "{{inputs.parameters.model_name}}"}'}
arguments:
parameters:
- {name: kernel}
- {name: model_name}
serviceAccountName: pipeline-runner
```
</details>
</p>
실행후 생성된 mlflow_pipeline.yaml 파일을 파이프라인 업로드한 후, 실행하여 run 의 결과를 확인합니다.

mlflow service를 포트포워딩해서 MLflow ui에 접속합니다.
```bash
kubectl port-forward svc/mlflow-server-service -n mlflow-system 5000:5000
```
웹 브라우저를 열어 localhost:5000으로 접속하면, 다음과 같이 run이 생성된 것을 확인할 수 있습니다.

run 을 클릭해서 확인하면 학습한 모델 파일이 있는 것을 확인할 수 있습니다.

================================================
FILE: docs/kubeflow/advanced-pipeline.md
================================================
---
title : "10. Pipeline - Setting"
description: ""
sidebar_position: 10
contributors: ["Jongseob Jeon"]
---
## Pipeline Setting
이번 페이지에서는 파이프라인에서 설정할 수 있는 값들에 대해 알아보겠습니다.
## Display Name
생성된 파이프라인 내에서 컴포넌트는 두 개의 이름을 갖습니다.
- task_name: 컴포넌트를 작성할 때 작성한 함수 이름
- display_name: kubeflow UI상에 보이는 이름
예를 들어서 다음과 같은 경우 두 컴포넌트 모두 Print and return number로 설정되어 있어서 어떤 컴포넌트가 1번인지 2번인지 확인하기 어렵습니다.

### set_display_name
이를 위한 것이 바로 display_name 입니다.
설정하는 방법은 파이프라인에서 컴포넌트에 다음과 같이 `set_display_name` [attribute](https://kubeflow-pipelines.readthedocs.io/en/latest/source/kfp.dsl.html#kfp.dsl.ContainerOp.set_display_name)를 이용하면 됩니다.
```python
import kfp
from kfp.components import create_component_from_func
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int):
print(number_1 + number_2)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1).set_display_name("This is number 1")
number_2_result = print_and_return_number(number_2).set_display_name("This is number 2")
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
).set_display_name("This is sum of number 1 and number 2")
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
이 스크립트를 실행해서 나온 `example_pipeline.yaml`을 확인하면 다음과 같습니다.
<p>
<details>
<summary>example_pipeline.yaml</summary>
```bash
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: example-pipeline-
annotations: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.9, pipelines.kubeflow.org/pipeline_compilation_time: '2021-12-09T18:11:43.193190',
pipelines.kubeflow.org/pipeline_spec: '{"inputs": [{"name": "number_1", "type":
"Integer"}, {"name": "number_2", "type": "Integer"}], "name": "example_pipeline"}'}
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.9}
spec:
entrypoint: example-pipeline
templates:
- name: example-pipeline
inputs:
parameters:
- {name: number_1}
- {name: number_2}
dag:
tasks:
- name: print-and-return-number
template: print-and-return-number
arguments:
parameters:
- {name: number_1, value: '{{inputs.parameters.number_1}}'}
- name: print-and-return-number-2
template: print-and-return-number-2
arguments:
parameters:
- {name: number_2, value: '{{inputs.parameters.number_2}}'}
- name: sum-and-print-numbers
template: sum-and-print-numbers
dependencies: [print-and-return-number, print-and-return-number-2]
arguments:
parameters:
- {name: print-and-return-number-2-Output, value: '{{tasks.print-and-return-number-2.outputs.parameters.print-and-return-number-2-Output}}'}
- {name: print-and-return-number-Output, value: '{{tasks.print-and-return-number.outputs.parameters.print-and-return-number-Output}}'}
- name: print-and-return-number
container:
args: [--number, '{{inputs.parameters.number_1}}', '----output-paths', /tmp/outputs/Output/data]
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def print_and_return_number(number):
print(number)
return number
def _serialize_int(int_value: int) -> str:
if isinstance(int_value, str):
return int_value
if not isinstance(int_value, int):
raise TypeError('Value "{}" has type "{}" instead of int.'.format(
str(int_value), str(type(int_value))))
return str(int_value)
import argparse
_parser = argparse.ArgumentParser(prog='Print and return number', description='')
_parser.add_argument("--number", dest="number", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)
_parsed_args = vars(_parser.parse_args())
_output_files = _parsed_args.pop("_output_paths", [])
_outputs = print_and_return_number(**_parsed_args)
_outputs = [_outputs]
_output_serializers = [
_serialize_int,
]
import os
for idx, output_file in enumerate(_output_files):
try:
os.makedirs(os.path.dirname(output_file))
except OSError:
pass
with open(output_file, 'w') as f:
f.write(_output_serializers[idx](_outputs[idx]))
image: python:3.7
inputs:
parameters:
- {name: number_1}
outputs:
parameters:
- name: print-and-return-number-Output
valueFrom: {path: /tmp/outputs/Output/data}
artifacts:
- {name: print-and-return-number-Output, path: /tmp/outputs/Output/data}
metadata:
annotations: {pipelines.kubeflow.org/task_display_name: This is number 1, pipelines.kubeflow.org/component_spec: '{"implementation":
{"container": {"args": ["--number", {"inputValue": "number"}, "----output-paths",
{"outputPath": "Output"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def print_and_return_number(number):\n print(number)\n return number\n\ndef
_serialize_int(int_value: int) -> str:\n if isinstance(int_value, str):\n return
int_value\n if not isinstance(int_value, int):\n raise TypeError(''Value
\"{}\" has type \"{}\" instead of int.''.format(\n str(int_value),
str(type(int_value))))\n return str(int_value)\n\nimport argparse\n_parser
= argparse.ArgumentParser(prog=''Print and return number'', description='''')\n_parser.add_argument(\"--number\",
dest=\"number\", type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"----output-paths\",
dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files
= _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = print_and_return_number(**_parsed_args)\n\n_outputs
= [_outputs]\n\n_output_serializers = [\n _serialize_int,\n\n]\n\nimport
os\nfor idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except
OSError:\n pass\n with open(output_file, ''w'') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number", "type": "Integer"}],
"name": "Print and return number", "outputs": [{"name": "Output", "type":
"Integer"}]}', pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number":
"{{inputs.parameters.number_1}}"}'}
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.9
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
- name: print-and-return-number-2
container:
args: [--number, '{{inputs.parameters.number_2}}', '----output-paths', /tmp/outputs/Output/data]
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def print_and_return_number(number):
print(number)
return number
def _serialize_int(int_value: int) -> str:
if isinstance(int_value, str):
return int_value
if not isinstance(int_value, int):
raise TypeError('Value "{}" has type "{}" instead of int.'.format(
str(int_value), str(type(int_value))))
return str(int_value)
import argparse
_parser = argparse.ArgumentParser(prog='Print and return number', description='')
_parser.add_argument("--number", dest="number", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)
_parsed_args = vars(_parser.parse_args())
_output_files = _parsed_args.pop("_output_paths", [])
_outputs = print_and_return_number(**_parsed_args)
_outputs = [_outputs]
_output_serializers = [
_serialize_int,
]
import os
for idx, output_file in enumerate(_output_files):
try:
os.makedirs(os.path.dirname(output_file))
except OSError:
pass
with open(output_file, 'w') as f:
f.write(_output_serializers[idx](_outputs[idx]))
image: python:3.7
inputs:
parameters:
- {name: number_2}
outputs:
parameters:
- name: print-and-return-number-2-Output
valueFrom: {path: /tmp/outputs/Output/data}
artifacts:
- {name: print-and-return-number-2-Output, path: /tmp/outputs/Output/data}
metadata:
annotations: {pipelines.kubeflow.org/task_display_name: This is number 2, pipelines.kubeflow.org/component_spec: '{"implementation":
{"container": {"args": ["--number", {"inputValue": "number"}, "----output-paths",
{"outputPath": "Output"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def print_and_return_number(number):\n print(number)\n return number\n\ndef
_serialize_int(int_value: int) -> str:\n if isinstance(int_value, str):\n return
int_value\n if not isinstance(int_value, int):\n raise TypeError(''Value
\"{}\" has type \"{}\" instead of int.''.format(\n str(int_value),
str(type(int_value))))\n return str(int_value)\n\nimport argparse\n_parser
= argparse.ArgumentParser(prog=''Print and return number'', description='''')\n_parser.add_argument(\"--number\",
dest=\"number\", type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"----output-paths\",
dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files
= _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = print_and_return_number(**_parsed_args)\n\n_outputs
= [_outputs]\n\n_output_serializers = [\n _serialize_int,\n\n]\n\nimport
os\nfor idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except
OSError:\n pass\n with open(output_file, ''w'') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number", "type": "Integer"}],
"name": "Print and return number", "outputs": [{"name": "Output", "type":
"Integer"}]}', pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number":
"{{inputs.parameters.number_2}}"}'}
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.9
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
- name: sum-and-print-numbers
container:
args: [--number-1, '{{inputs.parameters.print-and-return-number-Output}}', --number-2,
'{{inputs.parameters.print-and-return-number-2-Output}}']
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def sum_and_print_numbers(number_1, number_2):
print(number_1 + number_2)
import argparse
_parser = argparse.ArgumentParser(prog='Sum and print numbers', description='')
_parser.add_argument("--number-1", dest="number_1", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--number-2", dest="number_2", type=int, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = sum_and_print_numbers(**_parsed_args)
image: python:3.7
inputs:
parameters:
- {name: print-and-return-number-2-Output}
- {name: print-and-return-number-Output}
metadata:
annotations: {pipelines.kubeflow.org/task_display_name: This is sum of number
1 and number 2, pipelines.kubeflow.org/component_spec: '{"implementation":
{"container": {"args": ["--number-1", {"inputValue": "number_1"}, "--number-2",
{"inputValue": "number_2"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def sum_and_print_numbers(number_1, number_2):\n print(number_1 + number_2)\n\nimport
argparse\n_parser = argparse.ArgumentParser(prog=''Sum and print numbers'',
description='''')\n_parser.add_argument(\"--number-1\", dest=\"number_1\",
type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--number-2\",
dest=\"number_2\", type=int, required=True, default=argparse.SUPPRESS)\n_parsed_args
= vars(_parser.parse_args())\n\n_outputs = sum_and_print_numbers(**_parsed_args)\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number_1", "type": "Integer"},
{"name": "number_2", "type": "Integer"}], "name": "Sum and print numbers"}',
pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number_1":
"{{inputs.parameters.print-and-return-number-Output}}", "number_2": "{{inputs.parameters.print-and-return-number-2-Output}}"}'}
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.9
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
arguments:
parameters:
- {name: number_1}
- {name: number_2}
serviceAccountName: pipeline-runner
```
</details>
</p>
이 전의 파일과 비교하면 `pipelines.kubeflow.org/task_display_name` key가 새로 생성되었습니다.
### UI in Kubeflow
위에서 만든 파일을 이용해 이전에 생성한 [파이프라인](../kubeflow/basic-pipeline-upload.md#upload-pipeline-version)의 버전을 올리겠습니다.

그러면 위와 같이 설정한 이름이 노출되는 것을 확인할 수 있습니다.
## Resources
### GPU
특별한 설정이 없다면 파이프라인은 컴포넌트를 쿠버네티스 파드(pod)로 실행할 때, 기본 리소스 스펙으로 실행하게 됩니다.
만약 GPU를 사용해 모델을 학습해야 할 때 쿠버네티스상에서 GPU를 할당받지 못해 제대로 학습이 이루어지지 않습니다.
이를 위해 `set_gpu_limit()` [attribute](https://kubeflow-pipelines.readthedocs.io/en/latest/source/kfp.dsl.html?highlight=set_gpu_limit#kfp.dsl.UserContainer.set_gpu_limit)을 이용해 설정할 수 있습니다.
```python
import kfp
from kfp.components import create_component_from_func
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int):
print(number_1 + number_2)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1).set_display_name("This is number 1")
number_2_result = print_and_return_number(number_2).set_display_name("This is number 2")
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
).set_display_name("This is sum of number 1 and number 2").set_gpu_limit(1)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
위의 스크립트를 실행하면 생성된 파일에서 `sum-and-print-numbers`를 자세히 보면 resources에 `{nvidia.com/gpu: 1}` 도 추가된 것을 볼 수 있습니다.
이를 통해 GPU를 할당받을 수 있습니다.
```bash
- name: sum-and-print-numbers
container:
args: [--number-1, '{{inputs.parameters.print-and-return-number-Output}}', --number-2,
'{{inputs.parameters.print-and-return-number-2-Output}}']
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def sum_and_print_numbers(number_1, number_2):
print(number_1 + number_2)
import argparse
_parser = argparse.ArgumentParser(prog='Sum and print numbers', description='')
_parser.add_argument("--number-1", dest="number_1", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--number-2", dest="number_2", type=int, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = sum_and_print_numbers(**_parsed_args)
image: python:3.7
resources:
limits: {nvidia.com/gpu: 1}
```
### CPU
cpu의 개수를 정하기 위해서 이용하는 함수는 `.set_cpu_limit()` [attribute](https://kubeflow-pipelines.readthedocs.io/en/latest/source/kfp.dsl.html?highlight=set_gpu_limit#kfp.dsl.Sidecar.set_cpu_limit)을 이용해 설정할 수 있습니다.
gpu와는 다른 점은 int가 아닌 string으로 입력해야 한다는 점입니다.
```python
import kfp
from kfp.components import create_component_from_func
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int):
print(number_1 + number_2)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1).set_display_name("This is number 1")
number_2_result = print_and_return_number(number_2).set_display_name("This is number 2")
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
).set_display_name("This is sum of number 1 and number 2").set_gpu_limit(1).set_cpu_limit("16")
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
바뀐 부분만 확인하면 다음과 같습니다.
```bash
resources:
limits: {nvidia.com/gpu: 1, cpu: '16'}
```
### Memory
메모리는 `.set_memory_limit()` [attribute](https://kubeflow-pipelines.readthedocs.io/en/latest/source/kfp.dsl.html?highlight=set_gpu_limit#kfp.dsl.Sidecar.set_memory_limit)을 이용해 설정할 수 있습니다.
```python
import kfp
from kfp.components import create_component_from_func
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int):
print(number_1 + number_2)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1).set_display_name("This is number 1")
number_2_result = print_and_return_number(number_2).set_display_name("This is number 2")
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
).set_display_name("This is sum of number 1 and number 2").set_gpu_limit(1).set_memory_limit("1G")
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
바뀐 부분만 확인하면 다음과 같습니다.
```bash
resources:
limits: {nvidia.com/gpu: 1, memory: 1G}
```
================================================
FILE: docs/kubeflow/advanced-run.md
================================================
---
title : "11. Pipeline - Run Result"
description: ""
sidebar_position: 11
contributors: ["Jongseob Jeon", "SeungTae Kim"]
---
## Run Result
Run 실행 결과를 눌러보면 3개의 탭이 존재합니다.
각각 Graph, Run output, Config 입니다.

## Graph

그래프에서는 실행된 컴포넌트를 누르면 컴포넌트의 실행 정보를 확인할 수 있습니다.
### Input/Output
Input/Output 탭은 컴포넌트에서 사용한 Config들과 Input, Output Artifacts를 확인하고 다운로드 받을 수 있습니다.
### Logs
Logs에서는 파이썬 코드 실행 중 나오는 모든 stdout을 확인할 수 있습니다.
다만 pod은 일정 시간이 지난 후 지워지기 때문에 일정 시간이 지나면 이 탭에서는 확인할 수 없습니다.
이때는 Output artifacts의 main-logs에서 확인할 수 있습니다.
### Visualizations
Visualizations에서는 컴포넌트에서 생성된 플랏을 보여줍니다.
플랏을 생성하기 위해서는 `mlpipeline_ui_metadata: OutputPath("UI_Metadata")` argument로 보여주고 싶은 값을 저장하면 됩니다. 이 때 플랏의 형태는 html 포맷이어야 합니다.
변환하는 과정은 다음과 같습니다.
```python
@partial(
create_component_from_func,
packages_to_install=["matplotlib"],
)
def plot_linear(
mlpipeline_ui_metadata: OutputPath("UI_Metadata")
):
import base64
import json
from io import BytesIO
import matplotlib.pyplot as plt
plt.plot(x=[1, 2, 3], y=[1, 2,3])
tmpfile = BytesIO()
plt.savefig(tmpfile, format="png")
encoded = base64.b64encode(tmpfile.getvalue()).decode("utf-8")
html = f"<img src='data:image/png;base64,{encoded}'>"
metadata = {
"outputs": [
{
"type": "web-app",
"storage": "inline",
"source": html,
},
],
}
with open(mlpipeline_ui_metadata, "w") as html_writer:
json.dump(metadata, html_writer)
```
파이프라인으로 작성하면 다음과 같이 됩니다.
```python
from functools import partial
import kfp
from kfp.components import create_component_from_func, OutputPath
from kfp.dsl import pipeline
@partial(
create_component_from_func,
packages_to_install=["matplotlib"],
)
def plot_linear(mlpipeline_ui_metadata: OutputPath("UI_Metadata")):
import base64
import json
from io import BytesIO
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [1, 2, 3])
tmpfile = BytesIO()
plt.savefig(tmpfile, format="png")
encoded = base64.b64encode(tmpfile.getvalue()).decode("utf-8")
html = f"<img src='data:image/png;base64,{encoded}'>"
metadata = {
"outputs": [
{
"type": "web-app",
"storage": "inline",
"source": html,
},
],
}
with open(mlpipeline_ui_metadata, "w") as html_writer:
json.dump(metadata, html_writer)
@pipeline(name="plot_pipeline")
def plot_pipeline():
plot_linear()
if __name__ == "__main__":
kfp.compiler.Compiler().compile(plot_pipeline, "plot_pipeline.yaml")
```
이 스크립트를 실행해서 나온 `plot_pipeline.yaml`을 확인하면 다음과 같습니다.
<p>
<details>
<summary>plot_pipeline.yaml</summary>
```bash
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: plot-pipeline-
annotations: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.9, pipelines.kubeflow.org/pipeline_compilation_time: '2
022-01-17T13:31:32.963214',
pipelines.kubeflow.org/pipeline_spec: '{"name": "plot_pipeline"}'}
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.8.9}
spec:
entrypoint: plot-pipeline
templates:
- name: plot-linear
container:
args: [--mlpipeline-ui-metadata, /tmp/outputs/mlpipeline_ui_metadata/data]
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'matplotlib' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet
--no-warn-script-location 'matplotlib' --user) && "$0" "$@"
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def _make_parent_dirs_and_return_path(file_path: str):
import os
os.makedirs(os.path.dirname(file_path), exist_ok=True)
return file_path
def plot_linear(mlpipeline_ui_metadata):
import base64
import json
from io import BytesIO
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [1, 2, 3])
tmpfile = BytesIO()
plt.savefig(tmpfile, format="png")
encoded = base64.b64encode(tmpfile.getvalue()).decode("utf-8")
html = f"<img src='data:image/png;base64,{encoded}'>"
metadata = {
"outputs": [
{
"type": "web-app",
"storage": "inline",
"source": html,
},
],
}
with open(mlpipeline_ui_metadata, "w") as html_writer:
json.dump(metadata, html_writer)
import argparse
_parser = argparse.ArgumentParser(prog='Plot linear', description='')
_parser.add_argument("--mlpipeline-ui-metadata", dest="mlpipeline_ui_metadata", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = plot_linear(**_parsed_args)
image: python:3.7
outputs:
artifacts:
- {name: mlpipeline-ui-metadata, path: /tmp/outputs/mlpipeline_ui_metadata/data}
metadata:
labels:
pipelines.kubeflow.org/kfp_sdk_version: 1.8.9
pipelines.kubeflow.org/pipeline-sdk-type: kfp
pipelines.kubeflow.org/enable_caching: "true"
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--mlpipeline-ui-metadata", {"outputPath": "mlpipeline_ui_metadata"}],
"command": ["sh", "-c", "(PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip
install --quiet --no-warn-script-location ''matplotlib'' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location ''matplotlib''
--user) && \"$0\" \"$@\"", "sh", "-ec", "program_path=$(mktemp)\nprintf
\"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
"def _make_parent_dirs_and_return_path(file_path: str):\n import os\n os.makedirs(os.path.dirname(file_path),
exist_ok=True)\n return file_path\n\ndef plot_linear(mlpipeline_ui_metadata):\n import
base64\n import json\n from io import BytesIO\n\n import matplotlib.pyplot
as plt\n\n plt.plot([1, 2, 3], [1, 2, 3])\n\n tmpfile = BytesIO()\n plt.savefig(tmpfile,
format=\"png\")\n encoded = base64.b64encode(tmpfile.getvalue()).decode(\"utf-8\")\n\n html
= f\"<img src=''data:image/png;base64,{encoded}''>\"\n metadata = {\n \"outputs\":
[\n {\n \"type\": \"web-app\",\n \"storage\":
\"inline\",\n \"source\": html,\n },\n ],\n }\n with
open(mlpipeline_ui_metadata, \"w\") as html_writer:\n json.dump(metadata,
html_writer)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog=''Plot
linear'', description='''')\n_parser.add_argument(\"--mlpipeline-ui-metadata\",
dest=\"mlpipeline_ui_metadata\", type=_make_parent_dirs_and_return_path,
required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs
= plot_linear(**_parsed_args)\n"], "image": "python:3.7"}}, "name": "Plot
linear", "outputs": [{"name": "mlpipeline_ui_metadata", "type": "UI_Metadata"}]}',
pipelines.kubeflow.org/component_ref: '{}'}
- name: plot-pipeline
dag:
tasks:
- {name: plot-linear, template: plot-linear}
arguments:
parameters: []
serviceAccountName: pipeline-runner
```
</details>
</p>
실행 후 Visualization을 클릭합니다.

## Run output

Run output은 kubeflow에서 지정한 형태로 생긴 Artifacts를 모아서 보여주는 곳이며 평가 지표(Metric)를 보여줍니다.
평가 지표(Metric)을 보여주기 위해서는 `mlpipeline_metrics_path: OutputPath("Metrics")` argument에 보여주고 싶은 이름과 값을 json 형태로 저장하면 됩니다.
예를 들어서 다음과 같이 작성할 수 있습니다.
```python
@create_component_from_func
def show_metric_of_sum(
number: int,
mlpipeline_metrics_path: OutputPath("Metrics"),
):
import json
metrics = {
"metrics": [
{
"name": "sum_value",
"numberValue": number,
},
],
}
with open(mlpipeline_metrics_path, "w") as f:
json.dump(metrics, f)
```
평가 지표를 생성하는 컴포넌트를 [파이프라인](../kubeflow/basic-pipeline.md)에서 생성한 파이프라인에 추가 후 실행해 보겠습니다.
전체 파이프라인은 다음과 같습니다.
```python
import kfp
from kfp.components import create_component_from_func, OutputPath
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int) -> int:
sum_number = number_1 + number_2
print(sum_number)
return sum_number
@create_component_from_func
def show_metric_of_sum(
number: int,
mlpipeline_metrics_path: OutputPath("Metrics"),
):
import json
metrics = {
"metrics": [
{
"name": "sum_value",
"numberValue": number,
},
],
}
with open(mlpipeline_metrics_path, "w") as f:
json.dump(metrics, f)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
)
show_metric_of_sum(sum_result.output)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
실행 후 Run Output을 클릭하면 다음과 같이 나옵니다.

## Config

Config에서는 파이프라인 Config로 입력받은 모든 값을 확인할 수 있습니다.
================================================
FILE: docs/kubeflow/basic-component.md
================================================
---
title : "4. Component - Write"
description: ""
sidebar_position: 4
contributors: ["Jongseob Jeon"]
---
## Component
컴포넌트(Component)를 작성하기 위해서는 다음과 같은 내용을 작성해야 합니다.
1. 컴포넌트 콘텐츠(Component Contents) 작성
2. 컴포넌트 래퍼(Component Wrapper) 작성
이제 각 과정에 대해서 알아보도록 하겠습니다.
## Component Contents
컴포넌트 콘텐츠는 우리가 흔히 작성하는 파이썬 코드와 다르지 않습니다.
예를 들어서 숫자를 입력으로 받고 입력받은 숫자를 출력한 뒤 반환하는 컴포넌트를 작성해 보겠습니다.
파이썬 코드로 작성하면 다음과 같이 작성할 수 있습니다.
```python
print(number)
```
그런데 이 코드를 실행하면 에러가 나고 동작하지 않는데 그 이유는 출력해야 할 `number`가 정의되어 있지 않기 때문입니다.
[Kubeflow Concepts](../kubeflow/kubeflow-concepts.md)에서 `number` 와 같이 컴포넌트 콘텐츠에서 필요한 값들은 **Config**로 정의한다고 했습니다. 컴포넌트 콘텐츠를 실행시키기 위해 필요한 Config들은 컴포넌트 래퍼에서 전달이 되어야 합니다.
## Component Wrapper
### Define a standalone Python function
이제 필요한 Config를 전달할 수 있도록 컴포넌트 래퍼를 만들어야 합니다.
별도의 Config 없이 컴포넌트 래퍼로 감쌀 경우 다음과 같이 됩니다.
```python
def print_and_return_number():
print(number)
return number
```
이제 콘텐츠에서 필요한 Config를 래퍼의 argument로 추가합니다. 다만, argument 만을 적는 것이 아니라 argument의 타입 힌트도 작성해야 합니다. Kubeflow에서는 파이프라인을 Kubeflow 포맷으로 변환할 때, 컴포넌트 간의 연결에서 정해진 입력과 출력의 타입이 일치하는지 체크합니다. 만약 컴포넌트가 필요로 하는 입력과 다른 컴포넌트로부터 전달받은 출력의 포맷이 일치하지 않을 경우 파이프라인 생성을 할 수 없습니다.
이제 다음과 같이 argument와 그 타입, 그리고 반환하는 타입을 적어서 컴포넌트 래퍼를 완성합니다.
```python
def print_and_return_number(number: int) -> int:
print(number)
return number
```
Kubeflow에서 반환 값으로 사용할 수 있는 타입은 json에서 표현할 수 있는 타입들만 사용할 수 있습니다. 대표적으로 사용되며 권장하는 타입들은 다음과 같습니다.
- int
- float
- str
만약 단일 값이 아닌 여러 값을 반환하려면 `collections.namedtuple` 을 이용해야 합니다.
자세한 내용은 [Kubeflow 공식 문서](https://www.kubeflow.org/docs/components/pipelines/sdk/python-function-components/#passing-parameters-by-value)를 참고 하시길 바랍니다.
예를 들어서 입력받은 숫자를 2로 나눈 몫과 나머지를 반환하는 컴포넌트는 다음과 같이 작성해야 합니다.
```python
from typing import NamedTuple
def divide_and_return_number(
number: int,
) -> NamedTuple("DivideOutputs", [("quotient", int), ("remainder", int)]):
from collections import namedtuple
quotient, remainder = divmod(number, 2)
print("quotient is", quotient)
print("remainder is", remainder)
divide_outputs = namedtuple(
"DivideOutputs",
[
"quotient",
"remainder",
],
)
return divide_outputs(quotient, remainder)
```
### Convert to Kubeflow Format
이제 작성한 컴포넌트를 kubeflow에서 사용할 수 있는 포맷으로 변환해야 합니다. 변환은 `kfp.components.create_component_from_func` 를 통해서 할 수 있습니다.
이렇게 변환된 형태는 파이썬에서 함수로 import 하여서 파이프라인에서 사용할 수 있습니다.
```python
from kfp.components import create_component_from_func
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
```
### Share component with yaml file
만약 파이썬 코드로 공유를 할 수 없는 경우 YAML 파일로 컴포넌트를 공유해서 사용할 수 있습니다.
이를 위해서는 우선 컴포넌트를 YAML 파일로 변환한 뒤 `kfp.components.load_component_from_file` 을 통해 파이프라인에서 사용할 수 있습니다.
우선 작성한 컴포넌트를 YAML 파일로 변환하는 과정에 대해서 설명합니다.
```python
from kfp.components import create_component_from_func
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
if __name__ == "__main__":
print_and_return_number.component_spec.save("print_and_return_number.yaml")
```
작성한 파이썬 코드를 실행하면 `print_and_return_number.yaml` 파일이 생성됩니다. 파일을 확인하면 다음과 같습니다.
```bash
name: Print and return number
inputs:
- {name: number, type: Integer}
outputs:
- {name: Output, type: Integer}
implementation:
container:
image: python:3.7
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def print_and_return_number(number):
print(number)
return number
def _serialize_int(int_value: int) -> str:
if isinstance(int_value, str):
return int_value
if not isinstance(int_value, int):
raise TypeError('Value "{}" has type "{}" instead of int.'.format(str(int_value), str(type(int_value))))
return str(int_value)
import argparse
_parser = argparse.ArgumentParser(prog='Print and return number', description='')
_parser.add_argument("--number", dest="number", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)
_parsed_args = vars(_parser.parse_args())
_output_files = _parsed_args.pop("_output_paths", [])
_outputs = print_and_return_number(**_parsed_args)
_outputs = [_outputs]
_output_serializers = [
_serialize_int,
]
import os
for idx, output_file in enumerate(_output_files):
try:
os.makedirs(os.path.dirname(output_file))
except OSError:
pass
with open(output_file, 'w') as f:
f.write(_output_serializers[idx](_outputs[idx]))
args:
- --number
- {inputValue: number}
- '----output-paths'
- {outputPath: Output}
```
이제 생성된 파일을 공유해서 파이프라인에서 다음과 같이 사용할 수 있습니다.
```python
from kfp.components import load_component_from_file
print_and_return_number = load_component_from_file("print_and_return_number.yaml")
```
## How Kubeflow executes component
Kubeflow에서 컴포넌트가 실행되는 순서는 다음과 같습니다.
1. `docker pull <image>`: 정의된 컴포넌트의 실행 환경 정보가 담긴 이미지를 pull
2. run `command`: pull 한 이미지에서 컴포넌트 콘텐츠를 실행합니다.
`print_and_return_number.yaml` 를 예시로 들자면 `@create_component_from_func` 의 default image 는 python:3.7 이므로 해당 이미지를 기준으로 컴포넌트 콘텐츠를 실행하게 됩니다.
1. `docker pull python:3.7`
2. `print(number)`
## References:
- [Getting Started With Python function based components](https://www.kubeflow.org/docs/components/pipelines/sdk/python-function-components/#getting-started-with-python-function-based-components)
================================================
FILE: docs/kubeflow/basic-pipeline-upload.md
================================================
---
title : "6. Pipeline - Upload"
description: ""
sidebar_position: 6
contributors: ["Jongseob Jeon"]
---
## Upload Pipeline
이제 우리가 만든 파이프라인을 직접 kubeflow에서 업로드 해 보겠습니다.
파이프라인 업로드는 kubeflow 대시보드 UI를 통해 진행할 수 있습니다.
[Install Kubeflow](../setup-components/install-components-kf.md#정상-설치-확인) 에서 사용한 방법을 이용해 포트포워딩합니다.
```bash
kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80
```
[http://localhost:8080](http://localhost:8080)에 접속해 대시보드를 열어줍니다.
### 1. Pipelines 탭 선택

### 2. Upload Pipeline 선택

### 3. Choose file 선택

### 4. 생성된 yaml파일 업로드

### 5. Create

## Upload Pipeline Version
업로드된 파이프라인은 업로드를 통해서 버전을 관리할 수 있습니다. 다만 깃헙과 같은 코드 차원의 버전 관리가 아닌 같은 이름의 파이프라인을 모아서 보여주는 역할을 합니다.
위의 예시에서 파이프라인을 업로드한 경우 다음과 같이 example_pipeline이 생성된 것을 확인할 수 있습니다.

클릭하면 다음과 같은 화면이 나옵니다.

Upload Version을 클릭하면 다음과 같이 파이프라인을 업로드할 수 있는 화면이 생성됩니다.

파이프라인을 업로드 합니다.

업로드된 경우 다음과 같이 파이프라인 버전을 확인할 수 있습니다.

================================================
FILE: docs/kubeflow/basic-pipeline.md
================================================
---
title : "5. Pipeline - Write"
description: ""
sidebar_position: 5
contributors: ["Jongseob Jeon"]
---
## Pipeline
컴포넌트는 독립적으로 실행되지 않고 파이프라인의 구성요소로써 실행됩니다. 그러므로 컴포넌트를 실행해 보려면 파이프라인을 작성해야 합니다.
그리고 파이프라인을 작성하기 위해서는 컴포넌트의 집합과 컴포넌트의 실행 순서가 필요합니다.
이번 페이지에서는 숫자를 입력받고 출력하는 컴포넌트와 두 개의 컴포넌트로부터 숫자를 받아서 합을 출력하는 컴포넌트가 있는 파이프라인을 만들어 보도록 하겠습니다.
## Component Set
우선 파이프라인에서 사용할 컴포넌트들을 작성합니다.
1. `print_and_return_number`
입력받은 숫자를 출력하고 반환하는 컴포넌트입니다.
컴포넌트가 입력받은 값을 반환하기 때문에 int를 return의 타입 힌트로 입력합니다.
```python
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
```
2. `sum_and_print_numbers`
입력받은 두 개의 숫자의 합을 출력하는 컴포넌트입니다.
이 컴포넌트 역시 두 숫자의 합을 반환하기 때문에 int를 return의 타입 힌트로 입력합니다.
```python
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int) -> int:
sum_num = number_1 + number_2
print(sum_num)
return sum_num
```
## Component Order
### Define Order
필요한 컴포넌트의 집합을 만들었으면, 다음으로는 이들의 순서를 정의해야 합니다.
이번 페이지에서 만들 파이프라인의 순서를 그림으로 표현하면 다음과 같이 됩니다.

### Single Output
이제 이 순서를 코드로 옮겨보겠습니다.
우선 위의 그림에서 `print_and_return_number_1` 과 `print_and_return_number_2` 를 작성하면 다음과 같이 됩니다.
```python
def example_pipeline():
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
```
컴포넌트를 실행하고 그 반환 값을 각각 `number_1_result` 와 `number_2_result` 에 저장합니다.
저장된 `number_1_result` 의 반환 값은 `number_1_resulst.output` 를 통해 사용할 수 있습니다.
### Multi Output
위의 예시에서 컴포넌트는 단일 값만을 반환하기 때문에 `output`을 이용해 바로 사용할 수 있습니다.
만약, 여러 개의 반환 값이 있다면 `outputs`에 저장이 되며 dict 타입이기에 key를 이용해 원하는 반환 값을 사용할 수 있습니다.
예를 들어서 앞에서 작성한 여러 개를 반환하는 [컴포넌트](../kubeflow/basic-component.md#define-a-standalone-python-function) 의 경우를 보겠습니다.
`divde_and_return_number` 의 return 값은 `quotient` 와 `remainder` 가 있습니다. 이 두 값을 `print_and_return_number` 에 전달하는 예시를 보면 다음과 같습니다.
```python
def multi_pipeline():
divided_result = divde_and_return_number(number)
num_1_result = print_and_return_number(divided_result.outputs["quotient"])
num_2_result = print_and_return_number(divided_result.outputs["remainder"])
```
`divde_and_return_number`의 결과를 `divided_result`에 저장하고 각각 `divided_result.outputs["quotient"]`, `divided_result.outputs["remainder"]`로 값을 가져올 수 있습니다.
### Write to python code
이제 다시 본론으로 돌아와서 이 두 값의 결과를 `sum_and_print_numbers` 에 전달합니다.
```python
def example_pipeline():
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
)
```
다음으로 각 컴포넌트에 필요한 Config들을 모아서 파이프라인 Config로 정의 합니다.
```python
def example_pipeline(number_1: int, number_2:int):
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
)
```
## Convert to Kubeflow Format
마지막으로 kubeflow에서 사용할 수 있는 형식으로 변환합니다. 변환은 `kfp.dsl.pipeline` 함수를 이용해 할 수 있습니다.
```python
from kfp.dsl import pipeline
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
)
```
Kubeflow에서 파이프라인을 실행하기 위해서는 yaml 형식으로만 가능하기 때문에 생성한 파이프라인을 정해진 yaml 형식으로 컴파일(Compile) 해 주어야 합니다.
컴파일은 다음 명령어를 이용해 생성할 수 있습니다.
```python
if __name__ == "__main__":
import kfp
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
## Conclusion
앞서 설명한 내용을 한 파이썬 코드로 모으면 다음과 같이 됩니다.
```python
import kfp
from kfp.components import create_component_from_func
from kfp.dsl import pipeline
@create_component_from_func
def print_and_return_number(number: int) -> int:
print(number)
return number
@create_component_from_func
def sum_and_print_numbers(number_1: int, number_2: int):
print(number_1 + number_2)
@pipeline(name="example_pipeline")
def example_pipeline(number_1: int, number_2: int):
number_1_result = print_and_return_number(number_1)
number_2_result = print_and_return_number(number_2)
sum_result = sum_and_print_numbers(
number_1=number_1_result.output, number_2=number_2_result.output
)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(example_pipeline, "example_pipeline.yaml")
```
컴파일된 결과를 보면 다음과 같습니다.
<details>
<summary>example_pipeline.yaml</summary>
```bash
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: example-pipeline-
annotations: {pipelines.kubeflow.org/kfp_sdk_version: 1.6.3, pipelines.kubeflow.org/pipeline_compilation_time: '2021-12-05T13:38:51.566777',
pipelines.kubeflow.org/pipeline_spec: '{"inputs": [{"name": "number_1", "type":
"Integer"}, {"name": "number_2", "type": "Integer"}], "name": "example_pipeline"}'}
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.6.3}
spec:
entrypoint: example-pipeline
templates:
- name: example-pipeline
inputs:
parameters:
- {name: number_1}
- {name: number_2}
dag:
tasks:
- name: print-and-return-number
template: print-and-return-number
arguments:
parameters:
- {name: number_1, value: '{{inputs.parameters.number_1}}'}
- name: print-and-return-number-2
template: print-and-return-number-2
arguments:
parameters:
- {name: number_2, value: '{{inputs.parameters.number_2}}'}
- name: sum-and-print-numbers
template: sum-and-print-numbers
dependencies: [print-and-return-number, print-and-return-number-2]
arguments:
parameters:
- {name: print-and-return-number-2-Output, value: '{{tasks.print-and-return-number-2.outputs.parameters.print-and-return-number-2-Output}}'}
- {name: print-and-return-number-Output, value: '{{tasks.print-and-return-number.outputs.parameters.print-and-return-number-Output}}'}
- name: print-and-return-number
container:
args: [--number, '{{inputs.parameters.number_1}}', '----output-paths', /tmp/outputs/Output/data]
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def print_and_return_number(number):
print(number)
return number
def _serialize_int(int_value: int) -> str:
if isinstance(int_value, str):
return int_value
if not isinstance(int_value, int):
raise TypeError('Value "{}" has type "{}" instead of int.'.format(str(int_value), str(type(int_value))))
return str(int_value)
import argparse
_parser = argparse.ArgumentParser(prog='Print and return number', description='')
_parser.add_argument("--number", dest="number", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)
_parsed_args = vars(_parser.parse_args())
_output_files = _parsed_args.pop("_output_paths", [])
_outputs = print_and_return_number(**_parsed_args)
_outputs = [_outputs]
_output_serializers = [
_serialize_int,
]
import os
for idx, output_file in enumerate(_output_files):
try:
os.makedirs(os.path.dirname(output_file))
except OSError:
pass
with open(output_file, 'w') as f:
f.write(_output_serializers[idx](_outputs[idx]))
image: python:3.7
inputs:
parameters:
- {name: number_1}
outputs:
parameters:
- name: print-and-return-number-Output
valueFrom: {path: /tmp/outputs/Output/data}
artifacts:
- {name: print-and-return-number-Output, path: /tmp/outputs/Output/data}
metadata:
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.6.3, pipelines.kubeflow.org/pipeline-sdk-type: kfp}
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--number", {"inputValue": "number"}, "----output-paths", {"outputPath":
"Output"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf \"%s\"
\"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n", "def
print_and_return_number(number):\n print(number)\n return number\n\ndef
_serialize_int(int_value: int) -> str:\n if isinstance(int_value, str):\n return
int_value\n if not isinstance(int_value, int):\n raise TypeError(''Value
\"{}\" has type \"{}\" instead of int.''.format(str(int_value), str(type(int_value))))\n return
str(int_value)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog=''Print
and return number'', description='''')\n_parser.add_argument(\"--number\",
dest=\"number\", type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"----output-paths\",
dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files
= _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = print_and_return_number(**_parsed_args)\n\n_outputs
= [_outputs]\n\n_output_serializers = [\n _serialize_int,\n\n]\n\nimport
os\nfor idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except
OSError:\n pass\n with open(output_file, ''w'') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number", "type": "Integer"}],
"name": "Print and return number", "outputs": [{"name": "Output", "type":
"Integer"}]}', pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number":
"{{inputs.parameters.number_1}}"}'}
- name: print-and-return-number-2
container:
args: [--number, '{{inputs.parameters.number_2}}', '----output-paths', /tmp/outputs/Output/data]
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def print_and_return_number(number):
print(number)
return number
def _serialize_int(int_value: int) -> str:
if isinstance(int_value, str):
return int_value
if not isinstance(int_value, int):
raise TypeError('Value "{}" has type "{}" instead of int.'.format(str(int_value), str(type(int_value))))
return str(int_value)
import argparse
_parser = argparse.ArgumentParser(prog='Print and return number', description='')
_parser.add_argument("--number", dest="number", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)
_parsed_args = vars(_parser.parse_args())
_output_files = _parsed_args.pop("_output_paths", [])
_outputs = print_and_return_number(**_parsed_args)
_outputs = [_outputs]
_output_serializers = [
_serialize_int,
]
import os
for idx, output_file in enumerate(_output_files):
try:
os.makedirs(os.path.dirname(output_file))
except OSError:
pass
with open(output_file, 'w') as f:
f.write(_output_serializers[idx](_outputs[idx]))
image: python:3.7
inputs:
parameters:
- {name: number_2}
outputs:
parameters:
- name: print-and-return-number-2-Output
valueFrom: {path: /tmp/outputs/Output/data}
artifacts:
- {name: print-and-return-number-2-Output, path: /tmp/outputs/Output/data}
metadata:
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.6.3, pipelines.kubeflow.org/pipeline-sdk-type: kfp}
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--number", {"inputValue": "number"}, "----output-paths", {"outputPath":
"Output"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf \"%s\"
\"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n", "def
print_and_return_number(number):\n print(number)\n return number\n\ndef
_serialize_int(int_value: int) -> str:\n if isinstance(int_value, str):\n return
int_value\n if not isinstance(int_value, int):\n raise TypeError(''Value
\"{}\" has type \"{}\" instead of int.''.format(str(int_value), str(type(int_value))))\n return
str(int_value)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog=''Print
and return number'', description='''')\n_parser.add_argument(\"--number\",
dest=\"number\", type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"----output-paths\",
dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files
= _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = print_and_return_number(**_parsed_args)\n\n_outputs
= [_outputs]\n\n_output_serializers = [\n _serialize_int,\n\n]\n\nimport
os\nfor idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except
OSError:\n pass\n with open(output_file, ''w'') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number", "type": "Integer"}],
"name": "Print and return number", "outputs": [{"name": "Output", "type":
"Integer"}]}', pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number":
"{{inputs.parameters.number_2}}"}'}
- name: sum-and-print-numbers
container:
args: [--number-1, '{{inputs.parameters.print-and-return-number-Output}}', --number-2,
'{{inputs.parameters.print-and-return-number-2-Output}}']
command:
- sh
- -ec
- |
program_path=$(mktemp)
printf "%s" "$0" > "$program_path"
python3 -u "$program_path" "$@"
- |
def sum_and_print_numbers(number_1, number_2):
print(number_1 + number_2)
import argparse
_parser = argparse.ArgumentParser(prog='Sum and print numbers', description='')
_parser.add_argument("--number-1", dest="number_1", type=int, required=True, default=argparse.SUPPRESS)
_parser.add_argument("--number-2", dest="number_2", type=int, required=True, default=argparse.SUPPRESS)
_parsed_args = vars(_parser.parse_args())
_outputs = sum_and_print_numbers(**_parsed_args)
image: python:3.7
inputs:
parameters:
- {name: print-and-return-number-2-Output}
- {name: print-and-return-number-Output}
metadata:
labels: {pipelines.kubeflow.org/kfp_sdk_version: 1.6.3, pipelines.kubeflow.org/pipeline-sdk-type: kfp}
annotations: {pipelines.kubeflow.org/component_spec: '{"implementation": {"container":
{"args": ["--number-1", {"inputValue": "number_1"}, "--number-2", {"inputValue":
"number_2"}], "command": ["sh", "-ec", "program_path=$(mktemp)\nprintf \"%s\"
\"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n", "def
sum_and_print_numbers(number_1, number_2):\n print(number_1 + number_2)\n\nimport
argparse\n_parser = argparse.ArgumentParser(prog=''Sum and print numbers'',
description='''')\n_parser.add_argument(\"--number-1\", dest=\"number_1\",
type=int, required=True, default=argparse.SUPPRESS)\n_parser.add_argument(\"--number-2\",
dest=\"number_2\", type=int, required=True, default=argparse.SUPPRESS)\n_parsed_args
= vars(_parser.parse_args())\n\n_outputs = sum_and_print_numbers(**_parsed_args)\n"],
"image": "python:3.7"}}, "inputs": [{"name": "number_1", "type": "Integer"},
{"name": "number_2", "type": "Integer"}], "name": "Sum and print numbers"}',
pipelines.kubeflow.org/component_ref: '{}', pipelines.kubeflow.org/arguments.parameters: '{"number_1":
"{{inputs.parameters.print-and-return-number-Output}}", "number_2": "{{inputs.parameters.print-and-return-number-2-Output}}"}'}
arguments:
parameters:
- {name: number_1}
- {name: number_2}
serviceAccountName: pipeline-runner
```
</details>
================================================
FILE: docs/kubeflow/basic-requirements.md
================================================
---
title : "3. Install Requirements"
description: ""
sidebar_position: 3
contributors: ["Jongseob Jeon"]
---
실습을 위해 권장하는 파이썬 버전은 python>=3.7입니다. 파이썬 환경에 익숙하지 않은 분들은 다음 [Appendix 1. 파이썬 가상환경](../appendix/pyenv)을 참고하여 **클라이언트 노드**에 설치해주신 뒤 패키지 설치를 진행해주시기를 바랍니다.
실습을 진행하기에서 필요한 패키지들과 버전은 다음과 같습니다.
- requirements.txt
```bash
kfp==1.8.9
scikit-learn==1.0.1
mlflow==1.21.0
pandas==1.3.4
dill==0.3.4
```
[앞에서 만든 파이썬 가상환경](../appendix/pyenv.md#python-가상환경-생성)을 활성화합니다.
```bash
pyenv activate demo
```
패키지 설치를 진행합니다.
```bash
pip3 install -U pip
pip3 install kfp==1.8.9 scikit-learn==1.0.1 mlflow==1.21.0 pandas==1.3.4 dill==0.3.4
```
================================================
FILE: docs/kubeflow/basic-run.md
================================================
---
title : "7. Pipeline - Run"
description: ""
sidebar_position: 7
contributors: ["Jongseob Jeon"]
---
## Run Pipeline
이제 업로드한 파이프라인을 실행시켜 보겠습니다.
## Before Run
### 1. Create Experiment
Experiment란 Kubeflow 에서 실행되는 Run을 논리적으로 관리하는 단위입니다.
Kubeflow에서 namespace를 처음 들어오면 생성되어 있는 Experiment가 없습니다. 따라서 파이프라인을 실행하기 전에 미리 Experiment를 생성해두어야 합니다. Experiment이 있다면 [Run Pipeline](../kubeflow/basic-run.md#run-pipeline-1)으로 넘어가도 무방합니다.
Experiment는 Create Experiment 버튼을 통해 생성할 수 있습니다.

### 2. Name 입력
Experiment로 사용할 이름을 입력합니다.

## Run Pipeline
### 1. Create Run 선택

### 2. Experiment 선택


### 3. Pipeline Config 입력
파이프라인을 생성할 때 입력한 Config 값들을 채워 넣습니다.
업로드한 파이프라인은 number_1과 number_2를 입력해야 합니다.

### 4. Start
입력 후 Start 버튼을 누르면 파이프라인이 실행됩니다.

## Run Result
실행된 파이프라인들은 Runs 탭에서 확인할 수 있습니다.
Run을 클릭하면 실행된 파이프라인과 관련된 자세한 내용을 확인해 볼 수 있습니다.

클릭하면 다음과 같은 화면이 나옵니다. 아직 실행되지 않은 컴포넌트는 회색 표시로 나옵니다.

컴포넌트가 실행이 완료되면 초록색 체크 표시가 나옵니다.

가장 마지막 컴포넌트를 보면 입력한 Config인 3과 5의 합인 8이 출력된 것을 확인할 수 있습니다.

================================================
FILE: docs/kubeflow/how-to-debug.md
================================================
---
title : "13. Component - Debugging"
description: ""
sidebar_position: 13
contributors: ["Jongseob Jeon"]
---
## Debugging Pipeline
이번 페이지에서는 Kubeflow 컴포넌트를 디버깅하는 방법에 대해서 알아봅니다.
## Failed Component
이번 페이지에서는 [Component - MLFlow](../kubeflow/advanced-mlflow.md#mlflow-pipeline) 에서 이용한 파이프라인을 조금 수정해서 사용합니다.
우선 컴포넌트가 실패하도록 파이프라인을 변경하도록 하겠습니다.
```python
from functools import partial
import kfp
from kfp.components import InputPath, OutputPath, create_component_from_func
from kfp.dsl import pipeline
@partial(
create_component_from_func,
packages_to_install=["pandas", "scikit-learn"],
)
def load_iris_data(
data_path: OutputPath("csv"),
target_path: OutputPath("csv"),
):
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
data = pd.DataFrame(iris["data"], columns=iris["feature_names"])
target = pd.DataFrame(iris["target"], columns=["target"])
data["sepal length (cm)"] = None
data.to_csv(data_path, index=False)
target.to_csv(target_path, index=False)
@partial(
create_component_from_func,
packages_to_install=["pandas"],
)
def drop_na_from_csv(
data_path: InputPath("csv"),
output_path: OutputPath("csv"),
):
import pandas as pd
data = pd.read_csv(data_path)
data = data.dropna()
data.to_csv(output_path, index=False)
@partial(
create_component_from_func,
packages_to_install=["dill", "pandas", "scikit-learn", "mlflow"],
)
def train_from_csv(
train_data_path: InputPath("csv"),
train_target_path: InputPath("csv"),
model_path: OutputPath("dill"),
input_example_path: OutputPath("dill"),
signature_path: OutputPath("dill"),
conda_env_path: OutputPath("dill"),
kernel: str,
):
import dill
import pandas as pd
from sklearn.svm import SVC
from mlflow.models.signature import infer_signature
from mlflow.utils.environment import _mlflow_conda_env
train_data = pd.read_csv(train_data_path)
train_target = pd.read_csv(train_target_path)
clf = SVC(kernel=kernel)
clf.fit(train_data, train_target)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
input_example = train_data.sample(1)
with open(input_example_path, "wb") as file_writer:
dill.dump(input_example, file_writer)
signature = infer_signature(train_data, clf.predict(train_data))
with open(signature_path, "wb") as file_writer:
dill.dump(signature, file_writer)
conda_env = _mlflow_conda_env(
additional_pip_deps=["dill", "pandas", "scikit-learn"]
)
with open(conda_env_path, "wb") as file_writer:
dill.dump(conda_env, file_writer)
@pipeline(name="debugging_pipeline")
def debugging_pipeline(kernel: str):
iris_data = load_iris_data()
drop_data = drop_na_from_csv(data=iris_data.outputs["data"])
model = train_from_csv(
train_data=drop_data.outputs["output"],
train_target=iris_data.outputs["target"],
kernel=kernel,
)
if __name__ == "__main__":
kfp.compiler.Compiler().compile(debugging_pipeline, "debugging_pipeline.yaml")
```
수정한 점은 다음과 같습니다.
1. 데이터를 불러오는 `load_iris_data` 컴포넌트에서 `sepal length (cm)` 피처에 `None` 값을 주입
2. `drop_na_from_csv` 컴포넌트에서 `drop_na()` 함수를 이용해 na 값이 포함된 `row`를 제거
이제 파이프라인을 업로드하고 실행해 보겠습니다.
실행 후 Run을 눌러서 확인해보면 `Train from csv` 컴포넌트에서 실패했다고 나옵니다.

실패한 컴포넌트를 클릭하고 로그를 확인해서 실패한 이유를 확인해 보겠습니다.

로그를 확인하면 데이터의 개수가 0이여서 실행되지 않았다고 나옵니다.
분명 정상적으로 데이터를 전달했는데 왜 데이터의 개수가 0개일까요?
이제 입력받은 데이터에 어떤 문제가 있었는지 확인해 보겠습니다.
우선 컴포넌트를 클릭하고 Input/Ouput 탭에서 입력값으로 들어간 데이터들을 다운로드 받습니다.
다운로드는 빨간색 네모로 표시된 곳의 링크를 클릭하면 됩니다.

두 개의 파일을 같은 경로에 다운로드합니다.
그리고 해당 경로로 이동해서 파일을 확인합니다.
```bash
ls
```
다음과 같이 두 개의 파일이 있습니다.
```bash
drop-na-from-csv-output.tgz load-iris-data-target.tgz
```
압축을 풀어보겠습니다.
```bash
tar -xzvf load-iris-data-target.tgz ; mv data target.csv
tar -xzvf drop-na-from-csv-output.tgz ; mv data data.csv
```
그리고 이를 주피터 노트북을 이용해 컴포넌트 코드를 실행합니다.

디버깅을 해본 결과 dropna 할 때 column을 기준으로 drop을 해야 하는데 row를 기준으로 drop을 해서 데이터가 모두 사라졌습니다.
이제 문제의 원인을 알아냈으니 column을 기준으로 drop이 되게 컴포넌트를 수정합니다.
```python
@partial(
create_component_from_func,
packages_to_install=["pandas"],
)
def drop_na_from_csv(
data_path: InputPath("csv"),
output_path: OutputPath("csv"),
):
import pandas as pd
data = pd.read_csv(data_path)
data = data.dropna(axis="columns")
data.to_csv(output_path, index=False)
```
수정 후 파이프라인을 다시 업로드하고 실행하면 다음과 같이 정상적으로 수행하는 것을 확인할 수 있습니다.

================================================
FILE: docs/kubeflow/kubeflow-concepts.md
================================================
---
title : "2. Kubeflow Concepts"
description: ""
sidebar_position: 2
contributors: ["Jongseob Jeon"]
---
## Component
컴포넌트(Component)는 컴포넌트 콘텐츠(Component contents)와 컴포넌트 래퍼(Component wrapper)로 구성되어 있습니다.
하나의 컴포넌트는 컴포넌트 래퍼를 통해 kubeflow에 전달되며 전달된 컴포넌트는 정의된 컴포넌트 콘텐츠를 실행(execute)하고 아티팩트(artifacts)들을 생산합니다.

### Component Contents
컴포넌트 콘텐츠를 구성하는 것은 총 3가지가 있습니다.

1. Environemnt
2. Python code w\ Config
3. Generates Artifacts
예시와 함께 각 구성 요소가 어떤 것인지 알아보도록 하겠습니다.
다음과 같이 데이터를 불러와 SVC(Support Vector Classifier)를 학습한 후 SVC 모델을 저장하는 과정을 적은 파이썬 코드가 있습니다.
```python
import dill
import pandas as pd
from sklearn.svm import SVC
train_data = pd.read_csv(train_data_path)
train_target= pd.read_csv(train_target_path)
clf= SVC(
kernel=kernel
)
clf.fit(train_data)
with open(model_path, mode="wb") as file_writer:
dill.dump(clf, file_writer)
```
위의 파이썬 코드는 다음과 같이 컴포넌트 콘텐츠로 나눌 수 있습니다.

Environment는 파이썬 코드에서 사용하는 패키지들을 import하는 부분입니다.
다음으로 Python Code w\ Config 에서는 주어진 Config를 이용해 실제로 학습을 수행합니다.
마지막으로 아티팩트를 저장하는 과정이 있습니다.
### Component Wrapper
컴포넌트 래퍼는 컴포넌트 콘텐츠에 필요한 Config를 전달하고 실행시키는 작업을 합니다.

Kubeflow에서는 컴포넌트 래퍼를 위의 `train_svc_from_csv`와 같이 함수의 형태로 정의합니다.
컴포넌트 래퍼가 콘텐츠를 감싸면 다음과 같이 됩니다.

### Artifacts
위의 설명에서 컴포넌트는 아티팩트(Artifacts)를 생성한다고 했습니다. 아티팩트란 evaluation result, log 등 어떤 형태로든 파일로 생성되는 것을 통틀어서 칭하는 용어입니다.
그중 우리가 관심을 두는 유의미한 것들은 다음과 같은 것들이 있습니다.

- Model
- Data
- Metric
- etc
#### Model
저희는 모델을 다음과 같이 정의 했습니다.
> 모델이란 파이썬 코드와 학습된 Weights와 Network 구조 그리고 이를 실행시키기 위한 환경이 모두 포함된 형태
#### Data
데이터는 전 처리된 피처, 모델의 예측 값 등을 포함합니다.
#### Metric
Metric은 동적 지표와 정적 지표 두 가지로 나누었습니다.
- 동적 지표란 train loss와 같이 학습이 진행되는 중 에폭(Epoch)마다 계속해서 변화하는 값을 의미합니다.
- 정적 지표란 학습이 끝난 후 최종적으로 모델을 평가하는 정확도 등을 의미합니다.
## Pipeline
파이프라인은 컴포넌트의 집합과 컴포넌트를 실행시키는 순서도로 구성되어 있습니다. 이 때, 순서도는 방향 순환이 없는 그래프로 이루어져 있으며, 간단한 조건문을 포함할 수 있습니다.

### Pipeline Config
앞서 컴포넌트를 실행시키기 위해서는 Config가 필요하다고 설명했습니다. 파이프라인을 구성하는 컴포넌트의 Config 들을 모아 둔 것이 파이프라인 Config입니다.

## Run
파이프라인이 필요로 하는 파이프라인 Config가 주어져야지만 파이프라인을 실행할 수 있습니다.
Kubeflow에서는 실행된 파이프라인을 Run 이라고 부릅니다.

파이프라인이 실행되면 각 컴포넌트가 아티팩트들을 생성합니다.
Kubeflow pipeline에서는 Run 하나당 고유한 ID 를 생성하고, Run에서 생성되는 모든 아티팩트들을 저장합니다.

그러면 이제 직접 컴포넌트와 파이프라인을 작성하는 방법에 대해서 알아보도록 하겠습니다.
================================================
FILE: docs/kubeflow/kubeflow-intro.md
================================================
---
title : "1. Kubeflow Introduction"
description: ""
sidebar_position: 1
contributors: ["Jongseob Jeon"]
---
Kubeflow를 사용하기 위해서는 컴포넌트(Component)와 파이프라인(Pipeline)을 작성해야 합니다.
*모두의 MLOps*에서 설명하는 방식은 [Kubeflow Pipeline 공식 홈페이지](https://www.kubeflow.org/docs/components/pipelines/overview/quickstart/)에서 설명하는 방식과는 다소 차이가 있습니다. 여기에서는 Kubeflow Pipeline을 워크플로(Workflow)가 아닌 앞서 설명한 [MLOps를 구성하는 요소](../kubeflow/kubeflow-concepts.md#component-contents) 중 하나의 컴포넌트로 사용하기 때문입니다.
그럼 이제 컴포넌트와 파이프라인은 무엇이며 어떻게 작성할 수 있는지 알아보도록 하겠습니다.
========================================
gitextract_cdatvxxx/ ├── .github/ │ ├── CODEOWNERS │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── deploy.yml │ └── pull-request.yml ├── .gitignore ├── README.md ├── babel.config.js ├── community/ │ ├── community.md │ ├── contributors.md │ └── how-to-contribute.md ├── docs/ │ ├── api-deployment/ │ │ ├── _category_.json │ │ ├── seldon-children.md │ │ ├── seldon-fields.md │ │ ├── seldon-iris.md │ │ ├── seldon-mlflow.md │ │ ├── seldon-pg.md │ │ └── what-is-api-deployment.md │ ├── appendix/ │ │ ├── _category_.json │ │ ├── metallb.md │ │ └── pyenv.md │ ├── further-readings/ │ │ ├── _category_.json │ │ └── info.md │ ├── introduction/ │ │ ├── _category_.json │ │ ├── component.md │ │ ├── intro.md │ │ ├── levels.md │ │ └── why_kubernetes.md │ ├── kubeflow/ │ │ ├── _category_.json │ │ ├── advanced-component.md │ │ ├── advanced-environment.md │ │ ├── advanced-mlflow.md │ │ ├── advanced-pipeline.md │ │ ├── advanced-run.md │ │ ├── basic-component.md │ │ ├── basic-pipeline-upload.md │ │ ├── basic-pipeline.md │ │ ├── basic-requirements.md │ │ ├── basic-run.md │ │ ├── how-to-debug.md │ │ ├── kubeflow-concepts.md │ │ └── kubeflow-intro.md │ ├── kubeflow-dashboard-guide/ │ │ ├── _category_.json │ │ ├── experiments-and-others.md │ │ ├── experiments.md │ │ ├── intro.md │ │ ├── notebooks.md │ │ ├── tensorboards.md │ │ └── volumes.md │ ├── prerequisites/ │ │ ├── _category_.json │ │ └── docker/ │ │ ├── _category_.json │ │ ├── advanced.md │ │ ├── command.md │ │ ├── docker.md │ │ ├── images.md │ │ ├── install.md │ │ └── introduction.md │ ├── setup-components/ │ │ ├── _category_.json │ │ ├── install-components-kf.md │ │ ├── install-components-mlflow.md │ │ ├── install-components-pg.md │ │ └── install-components-seldon.md │ └── setup-kubernetes/ │ ├── _category_.json │ ├── install-kubernetes/ │ │ ├── _category_.json │ │ ├── kubernetes-with-k3s.md │ │ ├── kubernetes-with-kubeadm.md │ │ └── kubernetes-with-minikube.md │ ├── install-kubernetes-module.md │ ├── install-prerequisite.md │ ├── intro.md │ ├── kubernetes.md │ └── setup-nvidia-gpu.md ├── docusaurus.config.js ├── i18n/ │ ├── en/ │ │ ├── code.json │ │ ├── docusaurus-plugin-content-blog/ │ │ │ └── options.json │ │ ├── docusaurus-plugin-content-docs/ │ │ │ ├── current/ │ │ │ │ ├── api-deployment/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── seldon-children.md │ │ │ │ │ ├── seldon-fields.md │ │ │ │ │ ├── seldon-iris.md │ │ │ │ │ ├── seldon-mlflow.md │ │ │ │ │ ├── seldon-pg.md │ │ │ │ │ └── what-is-api-deployment.md │ │ │ │ ├── appendix/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── metallb.md │ │ │ │ │ └── pyenv.md │ │ │ │ ├── further-readings/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── info.md │ │ │ │ ├── introduction/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── component.md │ │ │ │ │ ├── intro.md │ │ │ │ │ ├── levels.md │ │ │ │ │ └── why_kubernetes.md │ │ │ │ ├── kubeflow/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── advanced-component.md │ │ │ │ │ ├── advanced-environment.md │ │ │ │ │ ├── advanced-mlflow.md │ │ │ │ │ ├── advanced-pipeline.md │ │ │ │ │ ├── advanced-run.md │ │ │ │ │ ├── basic-component.md │ │ │ │ │ ├── basic-pipeline-upload.md │ │ │ │ │ ├── basic-pipeline.md │ │ │ │ │ ├── basic-requirements.md │ │ │ │ │ ├── basic-run.md │ │ │ │ │ ├── how-to-debug.md │ │ │ │ │ ├── kubeflow-concepts.md │ │ │ │ │ └── kubeflow-intro.md │ │ │ │ ├── kubeflow-dashboard-guide/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── experiments-and-others.md │ │ │ │ │ ├── experiments.md │ │ │ │ │ ├── intro.md │ │ │ │ │ ├── notebooks.md │ │ │ │ │ ├── tensorboards.md │ │ │ │ │ └── volumes.md │ │ │ │ ├── prerequisites/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── docker/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── advanced.md │ │ │ │ │ ├── command.md │ │ │ │ │ ├── docker.md │ │ │ │ │ ├── images.md │ │ │ │ │ ├── install.md │ │ │ │ │ └── introduction.md │ │ │ │ ├── setup-components/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── install-components-kf.md │ │ │ │ │ ├── install-components-mlflow.md │ │ │ │ │ ├── install-components-pg.md │ │ │ │ │ └── install-components-seldon.md │ │ │ │ └── setup-kubernetes/ │ │ │ │ ├── _category_.json │ │ │ │ ├── install-kubernetes/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── kubernetes-with-k3s.md │ │ │ │ │ ├── kubernetes-with-kubeadm.md │ │ │ │ │ └── kubernetes-with-minikube.md │ │ │ │ ├── install-kubernetes-module.md │ │ │ │ ├── install-prerequisite.md │ │ │ │ ├── intro.md │ │ │ │ ├── kubernetes.md │ │ │ │ └── setup-nvidia-gpu.md │ │ │ ├── current.json │ │ │ ├── version-1.0/ │ │ │ │ ├── api-deployment/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── seldon-children.md │ │ │ │ │ ├── seldon-fields.md │ │ │ │ │ ├── seldon-iris.md │ │ │ │ │ ├── seldon-mlflow.md │ │ │ │ │ ├── seldon-pg.md │ │ │ │ │ └── what-is-api-deployment.md │ │ │ │ ├── appendix/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── metallb.md │ │ │ │ │ └── pyenv.md │ │ │ │ ├── further-readings/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── info.md │ │ │ │ ├── introduction/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── component.md │ │ │ │ │ ├── intro.md │ │ │ │ │ ├── levels.md │ │ │ │ │ └── why_kubernetes.md │ │ │ │ ├── kubeflow/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── advanced-component.md │ │ │ │ │ ├── advanced-environment.md │ │ │ │ │ ├── advanced-mlflow.md │ │ │ │ │ ├── advanced-pipeline.md │ │ │ │ │ ├── advanced-run.md │ │ │ │ │ ├── basic-component.md │ │ │ │ │ ├── basic-pipeline-upload.md │ │ │ │ │ ├── basic-pipeline.md │ │ │ │ │ ├── basic-requirements.md │ │ │ │ │ ├── basic-run.md │ │ │ │ │ ├── how-to-debug.md │ │ │ │ │ ├── kubeflow-concepts.md │ │ │ │ │ └── kubeflow-intro.md │ │ │ │ ├── kubeflow-dashboard-guide/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── experiments-and-others.md │ │ │ │ │ ├── experiments.md │ │ │ │ │ ├── intro.md │ │ │ │ │ ├── notebooks.md │ │ │ │ │ ├── tensorboards.md │ │ │ │ │ └── volumes.md │ │ │ │ ├── prerequisites/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── docker/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── advanced.md │ │ │ │ │ ├── command.md │ │ │ │ │ ├── docker.md │ │ │ │ │ ├── images.md │ │ │ │ │ ├── install.md │ │ │ │ │ └── introduction.md │ │ │ │ ├── setup-components/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── install-components-kf.md │ │ │ │ │ ├── install-components-mlflow.md │ │ │ │ │ ├── install-components-pg.md │ │ │ │ │ └── install-components-seldon.md │ │ │ │ └── setup-kubernetes/ │ │ │ │ ├── _category_.json │ │ │ │ ├── install-kubernetes/ │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── kubernetes-with-k3s.md │ │ │ │ │ ├── kubernetes-with-kubeadm.md │ │ │ │ │ └── kubernetes-with-minikube.md │ │ │ │ ├── install-kubernetes-module.md │ │ │ │ ├── install-prerequisite.md │ │ │ │ ├── intro.md │ │ │ │ ├── kubernetes.md │ │ │ │ └── setup-nvidia-gpu.md │ │ │ └── version-1.0.json │ │ ├── docusaurus-plugin-content-docs-community/ │ │ │ ├── current/ │ │ │ │ └── community/ │ │ │ │ ├── community.md │ │ │ │ ├── contributors.md │ │ │ │ └── how-to-contribute.md │ │ │ └── current.json │ │ └── docusaurus-theme-classic/ │ │ ├── footer.json │ │ └── navbar.json │ └── ko/ │ ├── code.json │ ├── docusaurus-plugin-content-blog/ │ │ └── options.json │ ├── docusaurus-plugin-content-docs/ │ │ ├── current.json │ │ └── version-1.0.json │ ├── docusaurus-plugin-content-docs-community/ │ │ └── current.json │ └── docusaurus-theme-classic/ │ ├── footer.json │ └── navbar.json ├── package.json ├── python/ │ ├── env/ │ │ └── .gitkeep │ ├── pyproject.toml │ └── translation/ │ └── main.py ├── sidebars.js ├── sidebarsCommunity.js ├── src/ │ ├── components/ │ │ ├── HomepageFeatures/ │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ └── TeamProfileCards/ │ │ └── index.tsx │ ├── css/ │ │ └── custom.css │ └── pages/ │ ├── index.module.css │ ├── index.tsx │ └── markdown-page.md ├── static/ │ ├── .nojekyll │ ├── googlee5904fe980148e9b.html │ └── img/ │ └── site.webmanifest ├── tsconfig.json ├── versioned_docs/ │ └── version-1.0/ │ ├── api-deployment/ │ │ ├── _category_.json │ │ ├── seldon-children.md │ │ ├── seldon-fields.md │ │ ├── seldon-iris.md │ │ ├── seldon-mlflow.md │ │ ├── seldon-pg.md │ │ └── what-is-api-deployment.md │ ├── appendix/ │ │ ├── _category_.json │ │ ├── metallb.md │ │ └── pyenv.md │ ├── further-readings/ │ │ ├── _category_.json │ │ └── info.md │ ├── introduction/ │ │ ├── _category_.json │ │ ├── component.md │ │ ├── intro.md │ │ ├── levels.md │ │ └── why_kubernetes.md │ ├── kubeflow/ │ │ ├── _category_.json │ │ ├── advanced-component.md │ │ ├── advanced-environment.md │ │ ├── advanced-mlflow.md │ │ ├── advanced-pipeline.md │ │ ├── advanced-run.md │ │ ├── basic-component.md │ │ ├── basic-pipeline-upload.md │ │ ├── basic-pipeline.md │ │ ├── basic-requirements.md │ │ ├── basic-run.md │ │ ├── how-to-debug.md │ │ ├── kubeflow-concepts.md │ │ └── kubeflow-intro.md │ ├── kubeflow-dashboard-guide/ │ │ ├── _category_.json │ │ ├── experiments-and-others.md │ │ ├── experiments.md │ │ ├── intro.md │ │ ├── notebooks.md │ │ ├── tensorboards.md │ │ └── volumes.md │ ├── prerequisites/ │ │ ├── _category_.json │ │ └── docker/ │ │ ├── _category_.json │ │ ├── advanced.md │ │ ├── command.md │ │ ├── docker.md │ │ ├── images.md │ │ ├── install.md │ │ └── introduction.md │ ├── setup-components/ │ │ ├── _category_.json │ │ ├── install-components-kf.md │ │ ├── install-components-mlflow.md │ │ ├── install-components-pg.md │ │ └── install-components-seldon.md │ └── setup-kubernetes/ │ ├── _category_.json │ ├── install-kubernetes/ │ │ ├── _category_.json │ │ ├── kubernetes-with-k3s.md │ │ ├── kubernetes-with-kubeadm.md │ │ └── kubernetes-with-minikube.md │ ├── install-kubernetes-module.md │ ├── install-prerequisite.md │ ├── intro.md │ ├── kubernetes.md │ └── setup-nvidia-gpu.md ├── versioned_sidebars/ │ └── version-1.0-sidebars.json └── versions.json
SYMBOL INDEX (12 symbols across 4 files)
FILE: python/translation/main.py
function request_prompt (line 18) | def request_prompt(source_sentence):
function translate (line 28) | def translate(source_path, dest_path):
FILE: src/components/HomepageFeatures/index.tsx
type FeatureItem (line 5) | type FeatureItem = {
function Feature (line 40) | function Feature({title, Svg, description}: FeatureItem) {
function HomepageFeatures (line 54) | function HomepageFeatures(): JSX.Element {
FILE: src/components/TeamProfileCards/index.tsx
type ProfileProps (line 12) | type ProfileProps = {
function TeamProfileCard (line 20) | function TeamProfileCard({
function TeamProfileCardCol (line 66) | function TeamProfileCardCol(props: ProfileProps) {
function MainAuthorRow (line 72) | function MainAuthorRow(): JSX.Element {
function ContributorsRow (line 117) | function ContributorsRow(): JSX.Element {
FILE: src/pages/index.tsx
function HomepageHeader (line 10) | function HomepageHeader() {
function Home (line 30) | function Home(): JSX.Element {
Condensed preview — 291 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,651K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 29,
"preview": "* @mlops-for-all/maintainers\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 268,
"preview": "## Changes?\n<!-- 이 pr 로 인해서 무엇이 변경되었는지 작성해주세요 -->\n\n## Why we need?\n<!-- 이 pr 이 왜 필요한지 작성해주세요 -->\n\n## Test?\n\n- [ ] `npm r"
},
{
"path": ".github/workflows/deploy.yml",
"chars": 1428,
"preview": "name: Deploy to GitHub Pages\n\non:\n push:\n branches:\n - main\n # Review gh actions docs if you want to further"
},
{
"path": ".github/workflows/pull-request.yml",
"chars": 194,
"preview": "name: \"Pull Request\"\non:\n pull_request:\n types: [opened, synchronize, edited, reopened, closed]\n\njobs:\n label:\n "
},
{
"path": ".gitignore",
"chars": 277,
"preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
},
{
"path": "README.md",
"chars": 160,
"preview": "## 모두의 MLOps\n\n모두의 MLOps 프로젝트입니다.\n\n프로젝트에 누구던 자유롭게 기여할 수 있습니다.\n\n자세한 내용은 [How to Contribute](https://mlops-for-all.github.i"
},
{
"path": "babel.config.js",
"chars": 89,
"preview": "module.exports = {\n presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
},
{
"path": "community/community.md",
"chars": 679,
"preview": "---\ntitle: \"Community\"\nsidebar_position: 1\n---\n\n### *모두의 MLOps* 릴리즈 소식\n\n새로운 포스트나 수정사항은 [Announcements](https://github.co"
},
{
"path": "community/contributors.md",
"chars": 313,
"preview": "---\nsidebar_position: 3\n---\n\n# Contributors\n\n## Main Authors\n\nimport {\n MainAuthorRow,\n} from '@site/src/components/Tea"
},
{
"path": "community/how-to-contribute.md",
"chars": 1575,
"preview": "---\ntitle: \"How to Contribute\"\nsidebar_position: 2\n---\n\n## How to Start\n\n### Git Repo 준비\n\n1. [*모두의 MLOps* GitHub Reposit"
},
{
"path": "docs/api-deployment/_category_.json",
"chars": 96,
"preview": "{\n \"label\": \"API Deployment\",\n \"position\": 7,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/api-deployment/seldon-children.md",
"chars": 11885,
"preview": "---\ntitle : \"6. Multi Models\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Multi Models\n\n"
},
{
"path": "docs/api-deployment/seldon-fields.md",
"chars": 5898,
"preview": "---\ntitle : \"4. Seldon Fields\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## How Seldon Co"
},
{
"path": "docs/api-deployment/seldon-iris.md",
"chars": 4558,
"preview": "---\ntitle : \"2. Deploy SeldonDeployment\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "docs/api-deployment/seldon-mlflow.md",
"chars": 6128,
"preview": "---\ntitle : \"5. Model from MLflow\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Model fro"
},
{
"path": "docs/api-deployment/seldon-pg.md",
"chars": 962,
"preview": "---\ntitle : \"3. Seldon Monitoring\"\ndescription: \"Prometheus & Grafana 확인하기\"\nsidebar_position: 3\ndate: 2021-12-24\nlastmod"
},
{
"path": "docs/api-deployment/what-is-api-deployment.md",
"chars": 2046,
"preview": "---\ntitle : \"1. What is API Deployment?\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "docs/appendix/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Appendix\",\n \"position\": 9,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/appendix/metallb.md",
"chars": 12625,
"preview": "---\ntitle: \"2. Bare Metal 클러스터용 load balancer metallb 설치\"\nsidebar_position: 2\n---\n\n## MetalLB란?\n\nKubernetes 사용 시 AWS, GC"
},
{
"path": "docs/appendix/pyenv.md",
"chars": 8657,
"preview": "---\ntitle: \"1. Python 가상환경 설치\"\nsidebar_position: 1\n---\n\n## 파이썬 가상환경\n\nPython 환경을 사용하다 보면 여러 버전의 Python 환경을 사용하고 싶은 경우나, 여"
},
{
"path": "docs/further-readings/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Further Readings\",\n \"position\": 8,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/further-readings/info.md",
"chars": 2928,
"preview": "---\ntitle: \"다루지 못한 것들\"\ndate: 2021-12-21\nlastmod: 2021-12-21\n---\n\n## MLOps Component\n\n[MLOps Concepts](../introduction/co"
},
{
"path": "docs/introduction/_category_.json",
"chars": 94,
"preview": "{\n \"label\": \"Introduction\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/introduction/component.md",
"chars": 3381,
"preview": "---\ntitle : \"3. Components of MLOps\"\ndescription: \"Describe MLOps Components\"\nsidebar_position: 3\ndate: 2021-12-03\nlastm"
},
{
"path": "docs/introduction/intro.md",
"chars": 4578,
"preview": "---\ntitle : \"1. What is MLOps?\"\ndescription: \"Introduction to MLOps\"\nsidebar_position: 1\ndate: 2021-1./img to MLOps\"\nlas"
},
{
"path": "docs/introduction/levels.md",
"chars": 4228,
"preview": "---\ntitle : \"2. Levels of MLOps\"\ndescription: \"Levels of MLOps\"\nsidebar_position: 2\ndate: 2021-12-03\nlastmod: 2022-03-05"
},
{
"path": "docs/introduction/why_kubernetes.md",
"chars": 4044,
"preview": "---\ntitle : \"4. Why Kubernetes?\"\ndescription: \"Reason for using k8s in MLOps\"\nsidebar_position: 4\ndate: 2021-12-03\nlastm"
},
{
"path": "docs/kubeflow/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Kubeflow\",\n \"position\": 6,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/kubeflow/advanced-component.md",
"chars": 5286,
"preview": "---\ntitle : \"8. Component - InputPath/OutputPath\"\ndescription: \"\"\nsidebar_position: 8\ncontributors: [\"Jongseob Jeon\", \"S"
},
{
"path": "docs/kubeflow/advanced-environment.md",
"chars": 11433,
"preview": "---\ntitle : \"9. Component - Environment\"\ndescription: \"\"\nsidebar_position: 9\ncontributors: [\"Jongseob Jeon\"]\n---\n\n\n## Co"
},
{
"path": "docs/kubeflow/advanced-mlflow.md",
"chars": 36447,
"preview": "---\ntitle : \"12. Component - MLFlow\"\ndescription: \"\"\nsidebar_position: 12\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontribut"
},
{
"path": "docs/kubeflow/advanced-pipeline.md",
"chars": 19697,
"preview": "---\ntitle : \"10. Pipeline - Setting\"\ndescription: \"\"\nsidebar_position: 10\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeli"
},
{
"path": "docs/kubeflow/advanced-run.md",
"chars": 10200,
"preview": "---\ntitle : \"11. Pipeline - Run Result\"\ndescription: \"\"\nsidebar_position: 11\ncontributors: [\"Jongseob Jeon\", \"SeungTae K"
},
{
"path": "docs/kubeflow/basic-component.md",
"chars": 5712,
"preview": "---\ntitle : \"4. Component - Write\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\n\n## Componen"
},
{
"path": "docs/kubeflow/basic-pipeline-upload.md",
"chars": 1394,
"preview": "---\ntitle : \"6. Pipeline - Upload\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Upload Pi"
},
{
"path": "docs/kubeflow/basic-pipeline.md",
"chars": 16996,
"preview": "---\ntitle : \"5. Pipeline - Write\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeline\n\n"
},
{
"path": "docs/kubeflow/basic-requirements.md",
"chars": 649,
"preview": "---\ntitle : \"3. Install Requirements\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\"]\n---\n\n실습을 위해 권장"
},
{
"path": "docs/kubeflow/basic-run.md",
"chars": 1328,
"preview": "---\ntitle : \"7. Pipeline - Run\"\ndescription: \"\"\nsidebar_position: 7\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Run Pipeline"
},
{
"path": "docs/kubeflow/how-to-debug.md",
"chars": 4688,
"preview": "---\ntitle : \"13. Component - Debugging\"\ndescription: \"\"\nsidebar_position: 13\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Deb"
},
{
"path": "docs/kubeflow/kubeflow-concepts.md",
"chars": 2599,
"preview": "---\ntitle : \"2. Kubeflow Concepts\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Component"
},
{
"path": "docs/kubeflow/kubeflow-intro.md",
"chars": 523,
"preview": "---\ntitle : \"1. Kubeflow Introduction\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\"]\n---\n\nKubeflow"
},
{
"path": "docs/kubeflow-dashboard-guide/_category_.json",
"chars": 99,
"preview": "{\n \"label\": \"Kubeflow UI Guide\",\n \"position\": 5,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/kubeflow-dashboard-guide/experiments-and-others.md",
"chars": 476,
"preview": "---\ntitle : \"6. Kubeflow Pipeline 관련\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nCentral Das"
},
{
"path": "docs/kubeflow-dashboard-guide/experiments.md",
"chars": 505,
"preview": "---\ntitle : \"5. Experiments(AutoML)\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n다음으로는 Centra"
},
{
"path": "docs/kubeflow-dashboard-guide/intro.md",
"chars": 694,
"preview": "---\ntitle : \"1. Central Dashboard\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jaeyeon Kim\", \"SeungTae Kim\"]\n---"
},
{
"path": "docs/kubeflow-dashboard-guide/notebooks.md",
"chars": 3133,
"preview": "---\ntitle : \"2. Notebooks\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## 노트북 서버(Notebook Ser"
},
{
"path": "docs/kubeflow-dashboard-guide/tensorboards.md",
"chars": 851,
"preview": "---\ntitle : \"3. Tensorboards\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n다음으로는 Central Dashb"
},
{
"path": "docs/kubeflow-dashboard-guide/volumes.md",
"chars": 1497,
"preview": "---\ntitle : \"4. Volumes\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## Volumes\n\n다음으로는 Centra"
},
{
"path": "docs/prerequisites/_category_.json",
"chars": 95,
"preview": "{\n \"label\": \"Prerequisites\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/prerequisites/docker/_category_.json",
"chars": 88,
"preview": "{\n \"label\": \"Docker\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/prerequisites/docker/advanced.md",
"chars": 9127,
"preview": "---\ntitle : \"[Practice] Docker Advanced\"\ndescription: \"Practice to use docker more advanced way.\"\nsidebar_position: 6\nco"
},
{
"path": "docs/prerequisites/docker/command.md",
"chars": 10375,
"preview": "---\ntitle : \"[Practice] Docker command\"\ndescription: \"Practice to use docker command.\"\nsidebar_position: 4\ncontributors:"
},
{
"path": "docs/prerequisites/docker/docker.md",
"chars": 1683,
"preview": "---\ntitle : \"What is Docker?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "docs/prerequisites/docker/images.md",
"chars": 4368,
"preview": "---\ntitle : \"[Practice] Docker images\"\ndescription: \"Practice to use docker image.\"\nsidebar_position: 5\ncontributors: [\""
},
{
"path": "docs/prerequisites/docker/install.md",
"chars": 874,
"preview": "---\ntitle : \"Install Docker\"\ndescription: \"Install docker to start.\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "docs/prerequisites/docker/introduction.md",
"chars": 3432,
"preview": "---\ntitle : \"Why Docker & Kubernetes ?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 2\ncontributors: [\"Jongs"
},
{
"path": "docs/setup-components/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Components\",\n \"position\": 3,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/setup-components/install-components-kf.md",
"chars": 32048,
"preview": "---\ntitle : \"1. Kubeflow\"\ndescription: \"구성요소 설치 - Kubeflow\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ncon"
},
{
"path": "docs/setup-components/install-components-mlflow.md",
"chars": 4217,
"preview": "---\ntitle : \"2. MLflow Tracking Server\"\ndescription: \"구성요소 설치 - MLflow\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 20"
},
{
"path": "docs/setup-components/install-components-pg.md",
"chars": 3593,
"preview": "---\ntitle : \"4. Prometheus & Grafana\"\ndescription: \"구성요소 설치 - Prometheus & Grafana\"\nsidebar_position: 4\ndate: 2021-12-13"
},
{
"path": "docs/setup-components/install-components-seldon.md",
"chars": 4508,
"preview": "---\ntitle : \"3. Seldon-Core\"\ndescription: \"구성요소 설치 - Seldon-Core\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "docs/setup-kubernetes/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Kubernetes\",\n \"position\": 2,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/setup-kubernetes/install-kubernetes/_category_.json",
"chars": 103,
"preview": "{\n \"label\": \"4. Install Kubernetes\",\n \"position\": 4,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "docs/setup-kubernetes/install-kubernetes/kubernetes-with-k3s.md",
"chars": 2921,
"preview": "---\ntitle: \"4.1. K3s\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ndraft: false\nweight: 221\n"
},
{
"path": "docs/setup-kubernetes/install-kubernetes/kubernetes-with-kubeadm.md",
"chars": 3780,
"preview": "---\ntitle: \"4.3. Kubeadm\"\ndescription: \"\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Young"
},
{
"path": "docs/setup-kubernetes/install-kubernetes/kubernetes-with-minikube.md",
"chars": 6500,
"preview": "---\ntitle: \"4.2. Minikube\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Jaey"
},
{
"path": "docs/setup-kubernetes/install-kubernetes-module.md",
"chars": 4755,
"preview": "---\ntitle: \"5. Install Kubernetes Modules\"\ndescription: \"Install Helm, Kustomize\"\nsidebar_position: 5\ndate: 2021-12-13\nl"
},
{
"path": "docs/setup-kubernetes/install-prerequisite.md",
"chars": 5745,
"preview": "---\ntitle: \"3. Install Prerequisite\"\ndescription: \"Install docker\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2023-09"
},
{
"path": "docs/setup-kubernetes/intro.md",
"chars": 2780,
"preview": "---\ntitle: \"1. Introduction\"\ndescription: \"Setup Introduction\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-13\n"
},
{
"path": "docs/setup-kubernetes/kubernetes.md",
"chars": 1401,
"preview": "---\ntitle : \"2. Setup Kubernetes\"\ndescription: \"Setup Kubernetes\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "docs/setup-kubernetes/setup-nvidia-gpu.md",
"chars": 6973,
"preview": "---\ntitle: \"6. (Optional) Setup GPU\"\ndescription: \"Install nvidia docker, nvidia device plugin\"\nsidebar_position: 6\ndate"
},
{
"path": "docusaurus.config.js",
"chars": 4493,
"preview": "// @ts-check\n// Note: type annotations allow type checking and IDEs autocompletion\n\nconst lightCodeTheme = require(\"pris"
},
{
"path": "i18n/en/code.json",
"chars": 12384,
"preview": "{\n \"team.profile.Jongseob Jeon.body\": {\n \"message\": \"마키나락스에서 머신러닝 엔지니어로 일하고 있습니다. 모두의 딥러닝을 통해 많은 사람들이 딥러닝을 쉽게 접했듯이 M"
},
{
"path": "i18n/en/docusaurus-plugin-content-blog/options.json",
"chars": 318,
"preview": "{\n \"title\": {\n \"message\": \"Blog\",\n \"description\": \"The title for the blog used in SEO\"\n },\n \"description\": {\n "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/_category_.json",
"chars": 96,
"preview": "{\n \"label\": \"API Deployment\",\n \"position\": 7,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/seldon-children.md",
"chars": 12384,
"preview": "---\ntitle : \"6. Multi Models\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\nPreviously, the m"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/seldon-fields.md",
"chars": 7797,
"preview": "---\ntitle : \"4. Seldon Fields\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\nSummary of how S"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/seldon-iris.md",
"chars": 5365,
"preview": "---\ntitle : \"2. Deploy SeldonDeployment\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/seldon-mlflow.md",
"chars": 7055,
"preview": "---\ntitle : \"5. Model from MLflow\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Model fro"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/seldon-pg.md",
"chars": 1148,
"preview": "---\ntitle : \"3. Seldon Monitoring\"\ndescription: \"Prometheus & Grafana 확인하기\"\nsidebar_position: 3\ndate: 2021-12-24\nlastmod"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/api-deployment/what-is-api-deployment.md",
"chars": 3327,
"preview": "---\ntitle : \"1. What is API Deployment?\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/appendix/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Appendix\",\n \"position\": 9,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/appendix/metallb.md",
"chars": 14644,
"preview": "---\ntitle: \"2. Install load balancer metallb for Bare Metal Cluster\"\nsidebar_position: 2\n---\n\n## What is MetalLB?\n\n## In"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/appendix/pyenv.md",
"chars": 9744,
"preview": "---\ntitle: \"1. Install Python virtual environment\"\nsidebar_position: 1\n---\n\n## Python virtual environment\n\nWhen working "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/further-readings/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Further Readings\",\n \"position\": 8,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/further-readings/info.md",
"chars": 3945,
"preview": "---\ntitle: \"Further Readings\"\ndate: 2021-12-21\nlastmod: 2021-12-21\n---\n\n## MLOps Component\n\nFrom the components covered "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/introduction/_category_.json",
"chars": 94,
"preview": "{\n \"label\": \"Introduction\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/introduction/component.md",
"chars": 4904,
"preview": "---\ntitle : \"3. Components of MLOps\"\ndescription: \"Describe MLOps Components\"\nsidebar_position: 3\ndate: 2021-12-03\nlastm"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/introduction/intro.md",
"chars": 10197,
"preview": "---\ntitle : \"1. What is MLOps?\"\ndescription: \"Introduction to MLOps\"\nsidebar_position: 1\ndate: 2021-1./img to MLOps\"\nlas"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/introduction/levels.md",
"chars": 7698,
"preview": "---\ntitle : \"2. Levels of MLOps\"\ndescription: \"Levels of MLOps\"\nsidebar_position: 2\ndate: 2021-12-03\nlastmod: 2022-03-05"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/introduction/why_kubernetes.md",
"chars": 6226,
"preview": "---\ntitle : \"4. Why Kubernetes?\"\ndescription: \"Reason for using k8s in MLOps\"\nsidebar_position: 4\ndate: 2021-12-03\nlastm"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Kubeflow\",\n \"position\": 6,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/advanced-component.md",
"chars": 6569,
"preview": "---\ntitle : \"8. Component - InputPath/OutputPath\"\ndescription: \"\"\nsidebar_position: 8\ncontributors: [\"Jongseob Jeon\", \"S"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/advanced-environment.md",
"chars": 12632,
"preview": "---\ntitle : \"9. Component - Environment\"\ndescription: \"\"\nsidebar_position: 9\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Com"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/advanced-mlflow.md",
"chars": 37903,
"preview": "---\ntitle : \"12. Component - MLFlow\"\ndescription: \"\"\nsidebar_position: 12\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontribut"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/advanced-pipeline.md",
"chars": 20388,
"preview": "---\ntitle : \"10. Pipeline - Setting\"\ndescription: \"\"\nsidebar_position: 10\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeli"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/advanced-run.md",
"chars": 10879,
"preview": "---\ntitle : \"11. Pipeline - Run Result\"\ndescription: \"\"\nsidebar_position: 11\ncontributors: [\"Jongseob Jeon\", \"SeungTae K"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/basic-component.md",
"chars": 7065,
"preview": "---\ntitle : \"4. Component - Write\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\n\n## Componen"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/basic-pipeline-upload.md",
"chars": 1680,
"preview": "---\ntitle : \"6. Pipeline - Upload\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Upload Pi"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/basic-pipeline.md",
"chars": 18214,
"preview": "---\ntitle : \"5. Pipeline - Write\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeline\n\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/basic-requirements.md",
"chars": 848,
"preview": "---\ntitle : \"3. Install Requirements\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\"]\n---\n\nThe recom"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/basic-run.md",
"chars": 1801,
"preview": "---\ntitle : \"7. Pipeline - Run\"\ndescription: \"\"\nsidebar_position: 7\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Run Pipeline"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/how-to-debug.md",
"chars": 5257,
"preview": "---\ntitle : \"13. Component - Debugging\"\ndescription: \"\"\nsidebar_position: 13\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Deb"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/kubeflow-concepts.md",
"chars": 3630,
"preview": "---\ntitle : \"2. Kubeflow Concepts\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Component"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow/kubeflow-intro.md",
"chars": 639,
"preview": "---\ntitle : \"1. Kubeflow Introduction\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\"]\n---\n\nTo use K"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/_category_.json",
"chars": 99,
"preview": "{\n \"label\": \"Kubeflow UI Guide\",\n \"position\": 5,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/experiments-and-others.md",
"chars": 576,
"preview": "---\ntitle : \"6. Kubeflow Pipeline Relates\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nIn the"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/experiments.md",
"chars": 601,
"preview": "---\ntitle : \"5. Experiments(AutoML)\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nNext, we wil"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/intro.md",
"chars": 844,
"preview": "---\ntitle : \"1. Central Dashboard\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jaeyeon Kim\", \"SeungTae Kim\"]\n---"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/notebooks.md",
"chars": 4430,
"preview": "---\ntitle : \"2. Notebooks\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## Launch Notebook Ser"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/tensorboards.md",
"chars": 1052,
"preview": "---\ntitle : \"3. Tensorboards\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nLet's click on the "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/kubeflow-dashboard-guide/volumes.md",
"chars": 1846,
"preview": "---\ntitle : \"4. Volumes\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## Volumes\n\nNext, let's "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/_category_.json",
"chars": 95,
"preview": "{\n \"label\": \"Prerequisites\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/_category_.json",
"chars": 88,
"preview": "{\n \"label\": \"Docker\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/advanced.md",
"chars": 12043,
"preview": "---\ntitle : \"[Practice] Docker Advanced\"\ndescription: \"Practice to use docker more advanced way.\"\nsidebar_position: 6\nco"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/command.md",
"chars": 12663,
"preview": "---\ntitle : \"[Practice] Docker command\"\ndescription: \"Practice to use docker command.\"\nsidebar_position: 4\ncontributors:"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/docker.md",
"chars": 2758,
"preview": "---\ntitle : \"What is Docker?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/images.md",
"chars": 4440,
"preview": "---\ntitle : \"[Practice] Docker images\"\ndescription: \"Practice to use docker image.\"\nsidebar_position: 5\ncontributors: [\""
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/install.md",
"chars": 1129,
"preview": "---\ntitle : \"Install Docker\"\ndescription: \"Install docker to start.\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/prerequisites/docker/introduction.md",
"chars": 4699,
"preview": "---\ntitle : \"Why Docker & Kubernetes ?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 2\ncontributors: [\"Jongs"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-components/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Components\",\n \"position\": 3,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-components/install-components-kf.md",
"chars": 31460,
"preview": "---\ntitle : \"1. Kubeflow\"\ndescription: \"구성요소 설치 - Kubeflow\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ncon"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-components/install-components-mlflow.md",
"chars": 5209,
"preview": "---\ntitle : \"2. MLflow Tracking Server\"\ndescription: \"구성요소 설치 - MLflow\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 20"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-components/install-components-pg.md",
"chars": 4421,
"preview": "---\ntitle : \"4. Prometheus & Grafana\"\ndescription: \"구성요소 설치 - Prometheus & Grafana\"\nsidebar_position: 4\ndate: 2021-12-13"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-components/install-components-seldon.md",
"chars": 4889,
"preview": "---\ntitle : \"3. Seldon-Core\"\ndescription: \"구성요소 설치 - Seldon-Core\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Kubernetes\",\n \"position\": 2,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-kubernetes/_category_.json",
"chars": 103,
"preview": "{\n \"label\": \"4. Install Kubernetes\",\n \"position\": 4,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-kubernetes/kubernetes-with-k3s.md",
"chars": 3442,
"preview": "---\ntitle: \"4.1. K3s\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ndraft: false\nweight: 221\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-kubernetes/kubernetes-with-kubeadm.md",
"chars": 4586,
"preview": "---\ntitle: \"4.3. Kubeadm\"\ndescription: \"\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Young"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-kubernetes/kubernetes-with-minikube.md",
"chars": 7320,
"preview": "---\ntitle: \"4.2. Minikube\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Jaey"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-kubernetes-module.md",
"chars": 5539,
"preview": "---\ntitle: \"5. Install Kubernetes Modules\"\ndescription: \"Install Helm, Kustomize\"\nsidebar_position: 5\ndate: 2021-12-13\nl"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/install-prerequisite.md",
"chars": 6405,
"preview": "---\ntitle: \"3. Install Prerequisite\"\ndescription: \"Install docker\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/intro.md",
"chars": 3986,
"preview": "---\ntitle: \"1. Introduction\"\ndescription: \"Setup Introduction\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-13\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/kubernetes.md",
"chars": 2139,
"preview": "---\ntitle : \"2. Setup Kubernetes\"\ndescription: \"Setup Kubernetes\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current/setup-kubernetes/setup-nvidia-gpu.md",
"chars": 7576,
"preview": "---\ntitle: \"6. (Optional) Setup GPU\"\ndescription: \"Install nvidia docker, nvidia device plugin\"\nsidebar_position: 6\ndate"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/current.json",
"chars": 2196,
"preview": "{\n \"version.label\": {\n \"message\": \"Next\",\n \"description\": \"The label for version current\"\n },\n \"sidebar.tutoria"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/_category_.json",
"chars": 96,
"preview": "{\n \"label\": \"API Deployment\",\n \"position\": 7,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/seldon-children.md",
"chars": 12384,
"preview": "---\ntitle : \"6. Multi Models\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\nPreviously, the m"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/seldon-fields.md",
"chars": 7797,
"preview": "---\ntitle : \"4. Seldon Fields\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\nSummary of how S"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/seldon-iris.md",
"chars": 5365,
"preview": "---\ntitle : \"2. Deploy SeldonDeployment\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/seldon-mlflow.md",
"chars": 7055,
"preview": "---\ntitle : \"5. Model from MLflow\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Model fro"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/seldon-pg.md",
"chars": 1148,
"preview": "---\ntitle : \"3. Seldon Monitoring\"\ndescription: \"Prometheus & Grafana 확인하기\"\nsidebar_position: 3\ndate: 2021-12-24\nlastmod"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/api-deployment/what-is-api-deployment.md",
"chars": 3327,
"preview": "---\ntitle : \"1. What is API Deployment?\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-22\nlastmod: 2021-12-22\ncontri"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/appendix/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Appendix\",\n \"position\": 9,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/appendix/metallb.md",
"chars": 14644,
"preview": "---\ntitle: \"2. Install load balancer metallb for Bare Metal Cluster\"\nsidebar_position: 2\n---\n\n## What is MetalLB?\n\n## In"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/appendix/pyenv.md",
"chars": 9744,
"preview": "---\ntitle: \"1. Install Python virtual environment\"\nsidebar_position: 1\n---\n\n## Python virtual environment\n\nWhen working "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/further-readings/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Further Readings\",\n \"position\": 8,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/further-readings/info.md",
"chars": 3945,
"preview": "---\ntitle: \"Further Readings\"\ndate: 2021-12-21\nlastmod: 2021-12-21\n---\n\n## MLOps Component\n\nFrom the components covered "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/introduction/_category_.json",
"chars": 94,
"preview": "{\n \"label\": \"Introduction\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/introduction/component.md",
"chars": 4904,
"preview": "---\ntitle : \"3. Components of MLOps\"\ndescription: \"Describe MLOps Components\"\nsidebar_position: 3\ndate: 2021-12-03\nlastm"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/introduction/intro.md",
"chars": 10197,
"preview": "---\ntitle : \"1. What is MLOps?\"\ndescription: \"Introduction to MLOps\"\nsidebar_position: 1\ndate: 2021-1./img to MLOps\"\nlas"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/introduction/levels.md",
"chars": 7375,
"preview": "---\ntitle : \"2. Levels of MLOps\"\ndescription: \"Levels of MLOps\"\nsidebar_position: 2\ndate: 2021-12-03\nlastmod: 2022-03-05"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/introduction/why_kubernetes.md",
"chars": 6226,
"preview": "---\ntitle : \"4. Why Kubernetes?\"\ndescription: \"Reason for using k8s in MLOps\"\nsidebar_position: 4\ndate: 2021-12-03\nlastm"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/_category_.json",
"chars": 90,
"preview": "{\n \"label\": \"Kubeflow\",\n \"position\": 6,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/advanced-component.md",
"chars": 6569,
"preview": "---\ntitle : \"8. Component - InputPath/OutputPath\"\ndescription: \"\"\nsidebar_position: 8\ncontributors: [\"Jongseob Jeon\", \"S"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/advanced-environment.md",
"chars": 12632,
"preview": "---\ntitle : \"9. Component - Environment\"\ndescription: \"\"\nsidebar_position: 9\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Com"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/advanced-mlflow.md",
"chars": 37903,
"preview": "---\ntitle : \"12. Component - MLFlow\"\ndescription: \"\"\nsidebar_position: 12\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontribut"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/advanced-pipeline.md",
"chars": 20388,
"preview": "---\ntitle : \"10. Pipeline - Setting\"\ndescription: \"\"\nsidebar_position: 10\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeli"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/advanced-run.md",
"chars": 10879,
"preview": "---\ntitle : \"11. Pipeline - Run Result\"\ndescription: \"\"\nsidebar_position: 11\ncontributors: [\"Jongseob Jeon\", \"SeungTae K"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/basic-component.md",
"chars": 7065,
"preview": "---\ntitle : \"4. Component - Write\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jongseob Jeon\"]\n---\n\n\n## Componen"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/basic-pipeline-upload.md",
"chars": 1680,
"preview": "---\ntitle : \"6. Pipeline - Upload\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Upload Pi"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/basic-pipeline.md",
"chars": 18214,
"preview": "---\ntitle : \"5. Pipeline - Write\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Pipeline\n\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/basic-requirements.md",
"chars": 848,
"preview": "---\ntitle : \"3. Install Requirements\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\"]\n---\n\nThe recom"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/basic-run.md",
"chars": 1801,
"preview": "---\ntitle : \"7. Pipeline - Run\"\ndescription: \"\"\nsidebar_position: 7\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Run Pipeline"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/how-to-debug.md",
"chars": 5257,
"preview": "---\ntitle : \"13. Component - Debugging\"\ndescription: \"\"\nsidebar_position: 13\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Deb"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/kubeflow-concepts.md",
"chars": 3630,
"preview": "---\ntitle : \"2. Kubeflow Concepts\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jongseob Jeon\"]\n---\n\n## Component"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow/kubeflow-intro.md",
"chars": 639,
"preview": "---\ntitle : \"1. Kubeflow Introduction\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\"]\n---\n\nTo use K"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/_category_.json",
"chars": 99,
"preview": "{\n \"label\": \"Kubeflow UI Guide\",\n \"position\": 5,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/experiments-and-others.md",
"chars": 576,
"preview": "---\ntitle : \"6. Kubeflow Pipeline Relates\"\ndescription: \"\"\nsidebar_position: 6\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nIn the"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/experiments.md",
"chars": 601,
"preview": "---\ntitle : \"5. Experiments(AutoML)\"\ndescription: \"\"\nsidebar_position: 5\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nNext, we wil"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/intro.md",
"chars": 844,
"preview": "---\ntitle : \"1. Central Dashboard\"\ndescription: \"\"\nsidebar_position: 1\ncontributors: [\"Jaeyeon Kim\", \"SeungTae Kim\"]\n---"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/notebooks.md",
"chars": 4430,
"preview": "---\ntitle : \"2. Notebooks\"\ndescription: \"\"\nsidebar_position: 2\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## Launch Notebook Ser"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/tensorboards.md",
"chars": 1052,
"preview": "---\ntitle : \"3. Tensorboards\"\ndescription: \"\"\nsidebar_position: 3\ncontributors: [\"Jaeyeon Kim\"]\n---\n\nLet's click on the "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/kubeflow-dashboard-guide/volumes.md",
"chars": 1846,
"preview": "---\ntitle : \"4. Volumes\"\ndescription: \"\"\nsidebar_position: 4\ncontributors: [\"Jaeyeon Kim\"]\n---\n\n## Volumes\n\nNext, let's "
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/_category_.json",
"chars": 95,
"preview": "{\n \"label\": \"Prerequisites\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/_category_.json",
"chars": 88,
"preview": "{\n \"label\": \"Docker\",\n \"position\": 1,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/advanced.md",
"chars": 12043,
"preview": "---\ntitle : \"[Practice] Docker Advanced\"\ndescription: \"Practice to use docker more advanced way.\"\nsidebar_position: 6\nco"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/command.md",
"chars": 12663,
"preview": "---\ntitle : \"[Practice] Docker command\"\ndescription: \"Practice to use docker command.\"\nsidebar_position: 4\ncontributors:"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/docker.md",
"chars": 2758,
"preview": "---\ntitle : \"What is Docker?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 3\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/images.md",
"chars": 4440,
"preview": "---\ntitle : \"[Practice] Docker images\"\ndescription: \"Practice to use docker image.\"\nsidebar_position: 5\ncontributors: [\""
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/install.md",
"chars": 1129,
"preview": "---\ntitle : \"Install Docker\"\ndescription: \"Install docker to start.\"\nsidebar_position: 1\ncontributors: [\"Jongseob Jeon\","
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/prerequisites/docker/introduction.md",
"chars": 4699,
"preview": "---\ntitle : \"Why Docker & Kubernetes ?\"\ndescription: \"Introduction to Docker.\"\nsidebar_position: 2\ncontributors: [\"Jongs"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-components/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Components\",\n \"position\": 3,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-components/install-components-kf.md",
"chars": 31460,
"preview": "---\ntitle : \"1. Kubeflow\"\ndescription: \"구성요소 설치 - Kubeflow\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ncon"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-components/install-components-mlflow.md",
"chars": 5209,
"preview": "---\ntitle : \"2. MLflow Tracking Server\"\ndescription: \"구성요소 설치 - MLflow\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 20"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-components/install-components-pg.md",
"chars": 4421,
"preview": "---\ntitle : \"4. Prometheus & Grafana\"\ndescription: \"구성요소 설치 - Prometheus & Grafana\"\nsidebar_position: 4\ndate: 2021-12-13"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-components/install-components-seldon.md",
"chars": 4889,
"preview": "---\ntitle : \"3. Seldon-Core\"\ndescription: \"구성요소 설치 - Seldon-Core\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/_category_.json",
"chars": 98,
"preview": "{\n \"label\": \"Setup Kubernetes\",\n \"position\": 2,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-kubernetes/_category_.json",
"chars": 103,
"preview": "{\n \"label\": \"4. Install Kubernetes\",\n \"position\": 4,\n \"link\": {\n \"type\": \"generated-index\"\n }\n}\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-kubernetes/kubernetes-with-k3s.md",
"chars": 3442,
"preview": "---\ntitle: \"4.1. K3s\"\ndescription: \"\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-20\ndraft: false\nweight: 221\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-kubernetes/kubernetes-with-kubeadm.md",
"chars": 4586,
"preview": "---\ntitle: \"4.3. Kubeadm\"\ndescription: \"\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Young"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-kubernetes/kubernetes-with-minikube.md",
"chars": 7320,
"preview": "---\ntitle: \"4.2. Minikube\"\ndescription: \"\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-20\ncontributors: [\"Jaey"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-kubernetes-module.md",
"chars": 5539,
"preview": "---\ntitle: \"5. Install Kubernetes Modules\"\ndescription: \"Install Helm, Kustomize\"\nsidebar_position: 5\ndate: 2021-12-13\nl"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/install-prerequisite.md",
"chars": 6405,
"preview": "---\ntitle: \"3. Install Prerequisite\"\ndescription: \"Install docker\"\nsidebar_position: 3\ndate: 2021-12-13\nlastmod: 2021-12"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/intro.md",
"chars": 3986,
"preview": "---\ntitle: \"1. Introduction\"\ndescription: \"Setup Introduction\"\nsidebar_position: 1\ndate: 2021-12-13\nlastmod: 2021-12-13\n"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/kubernetes.md",
"chars": 2139,
"preview": "---\ntitle : \"2. Setup Kubernetes\"\ndescription: \"Setup Kubernetes\"\nsidebar_position: 2\ndate: 2021-12-13\nlastmod: 2021-12-"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0/setup-kubernetes/setup-nvidia-gpu.md",
"chars": 7576,
"preview": "---\ntitle: \"6. (Optional) Setup GPU\"\ndescription: \"Install nvidia docker, nvidia device plugin\"\nsidebar_position: 6\ndate"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs/version-1.0.json",
"chars": 1860,
"preview": "{\n \"version.label\": {\n \"message\": \"1.0\",\n \"description\": \"The label for version 1.0\"\n },\n \"sidebar.tutorialSide"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs-community/current/community/community.md",
"chars": 490,
"preview": "---\ntitle: \"Community\"\nsidebar_position: 1\n---\n\n### *MLOps for ALL* 릴리즈 소식\n\n새로운 포스트나 수정사항은 [Announcements](https://githu"
},
{
"path": "i18n/en/docusaurus-plugin-content-docs-community/current/community/contributors.md",
"chars": 313,
"preview": "---\nsidebar_position: 3\n---\n\n# Contributors\n\n## Main Authors\n\nimport {\n MainAuthorRow,\n} from '@site/src/components/Tea"
}
]
// ... and 91 more files (download for full content)
About this extraction
This page contains the full source code of the mlops-for-all/mlops-for-all.github.io GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 291 files (1.3 MB), approximately 400.6k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.