diff --git a/.gitignore b/.gitignore
index c61bf75d..be97a235 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ resources
# Local Netlify folder
.netlify
TODO
+.DS_Store
diff --git a/content/en/docs/kubeflow/advanced-component.md b/content/en/docs/kubeflow/advanced-component.md
new file mode 100644
index 00000000..87c3130d
--- /dev/null
+++ b/content/en/docs/kubeflow/advanced-component.md
@@ -0,0 +1,490 @@
+---
+title : "8. Component Envrionment"
+description: ""
+lead: ""
+draft: false
+weight: 321
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+이번 페이지에서는 [Kubeflow Concepts]({{< relref "docs/kubeflow/kubeflow-concepts.md#component-contents" >}})에서 예시로 나왔던 코드를 컴포넌트로 작성해 보겠습니다.
+
+## Component Contents
+
+아래 코드는 [Kubeflow Concepts]({{< relref "docs/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]({{< relref "docs/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은 입력 또는 출력하려고 하는 파일의 포맷입니다.
+그렇다고 꼭 이 포맷으로 파일 형태로 저장이 강제되는 것은 아닙니다.
+다만 파이프라인을 컴파일할 때 최소한의 타입 체크를 위한 도우미 역할을 합니다.
+만약 파일 포맷이 고정되지 않는다면 입력하지 않으면 됩니다 (type hint 에서 `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)
+
+
+if __name__ == "__main__":
+ train_from_csv.component_spec.save("train_from_csv.yaml")
+```
+
+이번에 알아볼 것은 다음으로 컴포넌트가 어떻게 컴포넌트 컨텐츠를 실행하는 지 알아보겠습니다.
+위의 스크립트를 실행하면 다음과 같은 `train_from_csv.yaml` 파일을 얻을 수 있습니다.
+
+```text
+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]({{< relref "docs/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들을 받을 수 있는 지 확인해 보겠습니다.
+
+```text
+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에 패키지들이 전부 설치 되어 있다면 추가적인 패키지 설치 없이 바로 사용할 수 있습니다.
+
+[도커 허브에 올리는 법 docker 를 만드는 법] ← 따로 글 만들어서
+
+다음과 같은 docker 파일을 작성 후 업로드 하도록 하겠습니다.
+
+```docker
+BASE_IMAGE=python:3.7
+
+RUN pip install dill pandas scikit-learn
+```
+
+이제 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")
+```
+
+이제 생성된 컴포넌트를 컴파일하면 다음과 같이 나옵니다.
+
+```text
+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` 파일이 생성됩니다.
+
+```text
+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 파일을 자세히 보면, 다음과 같은 줄이 자동으로 추가되어 필요한 패키지가 설치되기 때문에 오류 없이 정상적으로 실행됩니다.
+
+```text
+ 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" "$@"
+```
diff --git a/content/en/docs/kubeflow/advanced-mlflow.md b/content/en/docs/kubeflow/advanced-mlflow.md
new file mode 100644
index 00000000..f05bca5c
--- /dev/null
+++ b/content/en/docs/kubeflow/advanced-mlflow.md
@@ -0,0 +1,385 @@
+---
+title : "11. MLFlow Component"
+description: ""
+lead: ""
+draft: false
+weight: 329
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## MLFlow Component
+
+[Advanced Usage Component]({{< relref "docs/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 폴더가 생기며 아래와 같은 파일들이 생성됩니다.
+
+```text
+ls svc
+```
+
+위의 명령어를 실행할 경우 다음의 출력값을 확인할 수 있습니다.
+
+```text
+MLmodel conda.yaml input_example.json model.pkl requirements.txt
+```
+
+각 파일들을 확인하면 다음과 같습니다.
+
+- MLmodel
+
+ ```text
+ 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
+
+ ```text
+ channels:
+ - conda-forge
+ dependencies:
+ - python=3.8.10
+ - pip
+ - pip:
+ - mlflow
+ - dill
+ - pandas
+ - scikit-learn
+ name: mlflow-env
+ ```
+
+- input_example.json
+
+ ```text
+ {
+ "columns":
+ [
+ "sepal length (cm)",
+ "sepal width (cm)",
+ "petal length (cm)",
+ "petal width (cm)"
+ ],
+ "data":
+ [
+ [6.7, 3.1, 4.4, 1.4]
+ ]
+ }
+ ```
+
+- requirements.txt
+
+ ```text
+ 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에 업로드하는 컴포넌트를 작성합니다.
+
+```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"],
+)
+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 dill
+ import mlflow
+ from mlflow.sklearn import save_model
+
+ 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,
+ )
+ with mlflow.start_run():
+ mlflow.log_artifact(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)
+ target.to_csv(target_path)
+
+```
+
+### 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"],
+ )
+```
+
+한 가지 이상한 점을 확인하셨나요?
+바로 입력과 출력에서 받는 argument중 경로와 관련된 것들에 `_path` 접미사가 모두 사라졌습니다. 즉, `iris_data.outputs["data_path"]` 가 아닌 `iris_data.outputs["data"]` 로 접근하는 것을 확인할 수 있습니다.
+이는 kubeflow에서 정한 법칙으로 `InputPath` 와 `OutputPath` 로 생성된 경로들은 파이프라인에서 접근할 때는 접미사를 생략하여 접근합니다.
diff --git a/content/en/docs/kubeflow/advanced-pipeline.md b/content/en/docs/kubeflow/advanced-pipeline.md
new file mode 100644
index 00000000..f7ed57b3
--- /dev/null
+++ b/content/en/docs/kubeflow/advanced-pipeline.md
@@ -0,0 +1,491 @@
+---
+title : "9. Pipeline Setting"
+description: ""
+lead: ""
+draft: false
+weight: 323
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## 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`을 확인하면 다음과 같습니다.
+
+
+
+ example_pipeline.yaml
+
+```text
+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
+```
+
+
+
+
+이 전의 파일과 비교하면 `pipelines.kubeflow.org/task_display_name` key가 새로 생성되었습니다.
+
+### UI in Kubeflow
+
+위에서 만든 파일을 이용해 이전에 생성한 [파이프라인]({{< relref "docs/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를 할당 받을 수 있습니다.
+
+```text
+ - 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` 입니다.
+gpu와는 다른 점은 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")
+```
+
+바뀐 부분만 확인하면 다음과 같습니다.
+
+```text
+ resources:
+ limits: {nvidia.com/gpu: 1, cpu: '16'}
+```
+
+### Memory
+
+메모리는 `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")
+
+```
+
+바뀐 부분만 확인하면 다음과 같습니다.
+
+```text
+ resources:
+ limits: {nvidia.com/gpu: 1, memory: 1G}
+```
diff --git a/content/en/docs/kubeflow/advanced-run.md b/content/en/docs/kubeflow/advanced-run.md
new file mode 100644
index 00000000..2960d9c8
--- /dev/null
+++ b/content/en/docs/kubeflow/advanced-run.md
@@ -0,0 +1,232 @@
+---
+title : "10. Run Result"
+description: ""
+lead: ""
+draft: false
+weight: 324
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+
+## 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"
"
+ 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"
"
+ 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")
+```
+
+실행 후 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)
+```
+
+평가 지표를 생성하는 컴포넌트를 [파이프라인]({{< relref "docs/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로 입력받은 모든 값을 확인할 수 있습니다.
diff --git a/content/en/docs/kubeflow/basic-component.md b/content/en/docs/kubeflow/basic-component.md
new file mode 100644
index 00000000..5a342737
--- /dev/null
+++ b/content/en/docs/kubeflow/basic-component.md
@@ -0,0 +1,201 @@
+---
+title : "4. Write Component"
+description: ""
+lead: ""
+draft: false
+weight: 312
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+
+## Component
+
+컴포넌트(Component)를 작성하기 위해서는 다음과 같은 내용들을 작성해야 합니다.
+
+1. 컴포넌트 컨텐츠(Component Contents) 작성
+2. 컴포넌트 래퍼(Component Wrapper) 작성
+
+이제 각 과정들에 대해서 알아보도록 하겠습니다.
+
+## Component Contents
+
+컴포넌트 컨텐츠는 우리가 흔히 작성하는 파이썬 코드와 다르지 않습니다.
+예를 들어서 숫자를 입력으로 받고 입력받은 숫자를 출력한 뒤 반환하는 컴포넌트를 작성해 보겠습니다.
+파이썬 코드로 작성하면 다음과 같이 작성할 수 있습니다.
+
+```python
+print(number)
+```
+
+그런데 이 코드를 실행할 경우 에러가 나고 동작하지 않는데 그 이유는 출력해야 할 `number`가 정의되어 있지 않기 때문입니다.
+
+[Kubeflow Concepts]({{< relref "docs/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 divde_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` 파일이 생성됩니다. 파일을 확인하면 다음과 같습니다.
+
+```text
+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}
+```
+
+## How Kubeflow execute Component
+
+kubeflow에서 컴포넌트가 실행되는 순서는 정의된 컴포넌트의 실행 환경 정보가 담긴 이미지를 pull 후 해당 이미지에서 컴포넌트 컨텐츠를 실행합니다.
+`@create_component_from_func` 의 default image 는 python:3.7 이므로 해당 이미지를 기준으로 컴포넌트 컨텐츠를 실행하게 됩니다.
+`print_and_return_number.yaml` 를 예시로 들자면 실행되는 순서는 다음과 같습니다.
+
+1. `docker pull python:3.7`
+2. run `command`
+
+## 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)
diff --git a/content/en/docs/kubeflow/basic-pipeline-upload.md b/content/en/docs/kubeflow/basic-pipeline-upload.md
new file mode 100644
index 00000000..23d41e9d
--- /dev/null
+++ b/content/en/docs/kubeflow/basic-pipeline-upload.md
@@ -0,0 +1,79 @@
+---
+title : "6. Pipeline Upload"
+description: ""
+lead: ""
+draft: false
+weight: 314
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## Upload Pipeline
+
+이제 우리가 만든 파이프라인을 직접 kubeflow에서 업로드 해 보겠습니다.
+파이프라인 업로드는 kubeflow 대시보드 UI를 통해 진행할 수 있습니다.
+
+### 1. Pipelines 탭 선택
+
+
+
+
+
+### 2. Upload Pipeline 선택
+
+
+
+
+
+### 3. Choose file 선택
+
+
+
+
+
+### 4. 생성된 yaml파일 업로드
+
+
+
+
+
+### 5. Create
+
+
+
+
+
+## Upload Pipeline Version
+
+업로드된 파이프라인은 업로드를 통해서 버전을 관리할 수 있습니다. 다만 깃헙과 같은 코드 차원의 버전 관리가 아닌 같은 이름의 파이프라인을 모아서 보여주는 역할을 합니다.
+위의 예시에서 파이프라인을 업로드한 경우 다음과 같이 example_pipeline이 생성된 것을 확인할 수 있습니다.
+
+
+
+
+
+클릭하면 다음과 같은 화면이 나옵니다.
+
+
+
+
+
+Upload Version을 클릭하면 다음과 같이 파이프라인을 업로드할 수 있는 화면이 생성됩니다.
+
+
+
+
+
+파이프라인을 업로드 합니다.
+
+
+
+
+
+업로드 된 경우 다음과 같이 파이프라인 버전을 확인할 수 있습니다.
+
+
+
+
diff --git a/content/en/docs/kubeflow/basic-pipeline.md b/content/en/docs/kubeflow/basic-pipeline.md
new file mode 100644
index 00000000..2002f025
--- /dev/null
+++ b/content/en/docs/kubeflow/basic-pipeline.md
@@ -0,0 +1,399 @@
+---
+title : "5. Write Pipeline"
+description: ""
+lead: ""
+draft: false
+weight: 313
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## 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
+ ```
+
+1. `sum_and_print_numbers`
+
+ 입력 받은 두개의 숫자의 합을 출력하는 컴포넌트입니다.
+
+ ```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
+
+필요한 컴포넌트의 집합을 만들었으면, 다음으로는 이들의 순서를 정의해야 합니다.
+이번 페이지에서 만들 파이프라인의 순서를 그림으로 표현하면 다음과 같이 됩니다.
+
+
+
+
+
+이제 이 순서를 코드로 옮겨보겠습니다.
+우선 위의 그림에서 `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` 를 통해 사용할 수 있습니다.
+
+위의 예시에서 컴포넌트는 단일 값만을 반환하기 때문에 `output`을 이용해 바로 사용할 수 있습니다.
+만약, 여러 개의 반환값이 있다면 `outputs`에 저장이 됩니다.
+`outputs`는 dict 형태로서 key를 이용해 원하는 반환 값을 사용할 수 있습니다.
+
+이제 이 두 값의 결과를 `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")
+```
+
+컴파일된 결과를 보면 다음과 같습니다.
+
+
+ example_pipeline.yaml
+
+```text
+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
+```
+
+
diff --git a/content/en/docs/kubeflow/basic-requirements.md b/content/en/docs/kubeflow/basic-requirements.md
new file mode 100644
index 00000000..afa62417
--- /dev/null
+++ b/content/en/docs/kubeflow/basic-requirements.md
@@ -0,0 +1,32 @@
+---
+title : "3. Install Requirements"
+description: ""
+lead: ""
+draft: false
+weight: 311
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+실습을 위해 권장하는 파이썬 버전은 python>=3.7 입니다.
+
+실습을 진행하기에서 필요한 패키지들과 버전은 다음과 같습니다.
+
+- requirements.txt
+
+ ```text
+ kfp==1.8.9
+ scikit-learn==1.0.1
+ mlflow==1.21.0
+ pandas==1.3.4
+ dill==0.3.4
+ ```
+
+패키지 설치를 진행합니다.
+
+```text
+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
+```
diff --git a/content/en/docs/kubeflow/basic-run.md b/content/en/docs/kubeflow/basic-run.md
new file mode 100644
index 00000000..36bf74fc
--- /dev/null
+++ b/content/en/docs/kubeflow/basic-run.md
@@ -0,0 +1,95 @@
+---
+title : "7. Run Pipeline"
+description: ""
+lead: ""
+draft: false
+weight: 315
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## Run Pipeline
+
+이제 우리가 업로드한 파이프라인을 실행시켜 보겠습니다.
+
+## Before Run
+
+### 1. Create Experiment
+
+Experiment란 Kubeflow 에서 실행되는 Run을 논리적으로 관리하는 단위입니다.
+Kubeflow를 최초 실행한 경우 Experiment가 없습니다. 따라서 파이프라인을 최초 실행할 경우에는 Experiment를 먼저 생성해두어야 합니다.
+Experiment 를 미리 생성해 두었다면 [Run Pipeline]({{< relref "docs/kubeflow/basic-run.md#run-pipeline-1" >}})으로 넘어가도 무방합니다.
+
+Experiment는 Create Experiment 버튼을 통해 생성할 수 있습니다.
+
+
+
+
+
+### 2. Name 입력
+
+Experiment로 사용할 이름을 입력합니다.
+
+
+
+
+## Run Pipeline
+
+### 1. Create Run 선택
+
+
+
+
+
+### 2. Experiment 선택
+
+
+
+
+
+
+
+
+
+### 3. Pipeline Config 입력
+
+파이프라인을 생성할 때 입력한 Config 값들을 채워넣습니다.
+
+
+
+
+### 4. Start
+
+입력 후 Start 버튼을 누르면 파이프라인이 실행됩니다.
+
+
+
+
+## Run Result
+
+실행된 파이프라인들은 Runs 탭에서 확인할 수 있습니다.
+Run을 클릭하면 실행된 파이프라인과 관련된 자세한 내용을 확인해 볼 수 있습니다.
+
+
+
+
+
+클릭하면 다음과 같은 화면이 나옵니다. 아직 실행되지 않은 컴포넌트는 회색 표시로 나옵니다.
+
+
+
+
+
+컴포넌트가 실행이 완료되면 초록색 체크 표시가 나옵니다.
+
+
+
+
+
+가장 마지막 컴포넌트를 보면 입력한 Config인 3과 5의 합인 8이 출력된 것을 확인할 수 있습니다.
+
+
+
+
diff --git a/content/en/docs/kubeflow/example.md b/content/en/docs/kubeflow/example.md
deleted file mode 100644
index c379510a..00000000
--- a/content/en/docs/kubeflow/example.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title : "Kubeflow example"
-description: "Introduction to MLOps"
-lead: ""
-# date: 2020-10-06T08:48:23+00:00
-# lastmod: 2020-10-06T08:48:23+00:00
-draft: false
-weight: 301
-contributors: ["Jongseob Jeon"]
-menu:
- docs:
- parent: "kubeflow"
-images: []
----
-
-## 예시 페이지 입니다. 헤딩은 2번 부터 시작해주세요.
diff --git a/content/en/docs/kubeflow/how-to-debug.md b/content/en/docs/kubeflow/how-to-debug.md
new file mode 100644
index 00000000..d27cd0a3
--- /dev/null
+++ b/content/en/docs/kubeflow/how-to-debug.md
@@ -0,0 +1,14 @@
+---
+title : "12. Component Debugging"
+description: ""
+lead: ""
+draft: false
+weight: 330
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+
+이번 페이지에서는 Kubeflow 컴포넌트를 디버깅하는 방법에 대해서 알아봅니다.
diff --git a/content/en/docs/kubeflow/kubeflow-concepts.md b/content/en/docs/kubeflow/kubeflow-concepts.md
new file mode 100644
index 00000000..85abaf09
--- /dev/null
+++ b/content/en/docs/kubeflow/kubeflow-concepts.md
@@ -0,0 +1,143 @@
+---
+title : "2. Concepts"
+description: ""
+lead: ""
+draft: false
+weight: 303
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+## 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에서 생성되는 모든 아티팩트들을 저장합니다.
+
+
+
+
+
+그러면 이제 직접 컴포넌트와 파이프라인을 작성하는 방법에 대해서 알아보도록 하겠습니다.
diff --git a/content/en/docs/kubeflow/kubeflow-intro.md b/content/en/docs/kubeflow/kubeflow-intro.md
new file mode 100644
index 00000000..9cc455be
--- /dev/null
+++ b/content/en/docs/kubeflow/kubeflow-intro.md
@@ -0,0 +1,17 @@
+---
+title : "1. Kubeflow Introduction"
+description: ""
+lead: ""
+draft: false
+weight: 302
+contributors: ["Jongseob Jeon"]
+menu:
+ docs:
+ parent: "kubeflow"
+---
+
+Kubeflow를 사용하기 위해서는 컴포넌트(Component)와 파이프라인(Pipeline)을 작성해야 합니다.
+
+*모두의 MLOps*에서 설명하는 방식은 [Kubeflow Pipeline 공식 홈페이지](https://www.kubeflow.org/docs/components/pipelines/overview/quickstart/)에서 설명하는 방식과는 다소 차이가 있습니다. 여기에서는 Kubeflow Pipeline을 워크플로우(Workflow)가 아닌 앞서 설명한 [MLOps를 구성하는 요소]({{< relref "docs/kubeflow/kubeflow-concepts.md#component-contents" >}}) 중 하나의 컴포넌트로 사용하기 때문입니다.
+
+그럼 이제 컴포넌트와 파이프라인은 무엇이며 어떻게 작성할 수 있는지 알아보도록 하겠습니다.
diff --git a/static/images/docs/kubeflow/adv-pipeline-0.png b/static/images/docs/kubeflow/adv-pipeline-0.png
new file mode 100644
index 00000000..6f2ce2b2
Binary files /dev/null and b/static/images/docs/kubeflow/adv-pipeline-0.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-0.png b/static/images/docs/kubeflow/advanced-run-0.png
new file mode 100644
index 00000000..9f5f16a3
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-0.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-1.png b/static/images/docs/kubeflow/advanced-run-1.png
new file mode 100644
index 00000000..253186b2
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-1.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-2.png b/static/images/docs/kubeflow/advanced-run-2.png
new file mode 100644
index 00000000..bbdcaed0
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-2.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-3.png b/static/images/docs/kubeflow/advanced-run-3.png
new file mode 100644
index 00000000..58c520ee
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-3.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-4.png b/static/images/docs/kubeflow/advanced-run-4.png
new file mode 100644
index 00000000..93f29146
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-4.png differ
diff --git a/static/images/docs/kubeflow/advanced-run-5.png b/static/images/docs/kubeflow/advanced-run-5.png
new file mode 100644
index 00000000..23bcb734
Binary files /dev/null and b/static/images/docs/kubeflow/advanced-run-5.png differ
diff --git a/static/images/docs/kubeflow/concept-0.png b/static/images/docs/kubeflow/concept-0.png
new file mode 100644
index 00000000..ea024985
Binary files /dev/null and b/static/images/docs/kubeflow/concept-0.png differ
diff --git a/static/images/docs/kubeflow/concept-1.png b/static/images/docs/kubeflow/concept-1.png
new file mode 100644
index 00000000..c9b27c1c
Binary files /dev/null and b/static/images/docs/kubeflow/concept-1.png differ
diff --git a/static/images/docs/kubeflow/concept-2.png b/static/images/docs/kubeflow/concept-2.png
new file mode 100644
index 00000000..b1356c35
Binary files /dev/null and b/static/images/docs/kubeflow/concept-2.png differ
diff --git a/static/images/docs/kubeflow/concept-3.png b/static/images/docs/kubeflow/concept-3.png
new file mode 100644
index 00000000..4183aa73
Binary files /dev/null and b/static/images/docs/kubeflow/concept-3.png differ
diff --git a/static/images/docs/kubeflow/concept-4.png b/static/images/docs/kubeflow/concept-4.png
new file mode 100644
index 00000000..ca499e2b
Binary files /dev/null and b/static/images/docs/kubeflow/concept-4.png differ
diff --git a/static/images/docs/kubeflow/concept-5.png b/static/images/docs/kubeflow/concept-5.png
new file mode 100644
index 00000000..a15bb10c
Binary files /dev/null and b/static/images/docs/kubeflow/concept-5.png differ
diff --git a/static/images/docs/kubeflow/concept-6.png b/static/images/docs/kubeflow/concept-6.png
new file mode 100644
index 00000000..9d313c15
Binary files /dev/null and b/static/images/docs/kubeflow/concept-6.png differ
diff --git a/static/images/docs/kubeflow/concept-7.png b/static/images/docs/kubeflow/concept-7.png
new file mode 100644
index 00000000..60f4dbe0
Binary files /dev/null and b/static/images/docs/kubeflow/concept-7.png differ
diff --git a/static/images/docs/kubeflow/concept-8.png b/static/images/docs/kubeflow/concept-8.png
new file mode 100644
index 00000000..3766d1cc
Binary files /dev/null and b/static/images/docs/kubeflow/concept-8.png differ
diff --git a/static/images/docs/kubeflow/concept-9.png b/static/images/docs/kubeflow/concept-9.png
new file mode 100644
index 00000000..5b265bed
Binary files /dev/null and b/static/images/docs/kubeflow/concept-9.png differ
diff --git a/static/images/docs/kubeflow/mlflow-0.png b/static/images/docs/kubeflow/mlflow-0.png
new file mode 100644
index 00000000..8699a4d0
Binary files /dev/null and b/static/images/docs/kubeflow/mlflow-0.png differ
diff --git a/static/images/docs/kubeflow/pipeline-0.png b/static/images/docs/kubeflow/pipeline-0.png
new file mode 100644
index 00000000..104d3f7e
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-0.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-0.png b/static/images/docs/kubeflow/pipeline-gui-0.png
new file mode 100644
index 00000000..ad959cad
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-0.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-1.png b/static/images/docs/kubeflow/pipeline-gui-1.png
new file mode 100644
index 00000000..6f1e8d10
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-1.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-2.png b/static/images/docs/kubeflow/pipeline-gui-2.png
new file mode 100644
index 00000000..378ac16f
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-2.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-3.png b/static/images/docs/kubeflow/pipeline-gui-3.png
new file mode 100644
index 00000000..bc657211
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-3.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-4.png b/static/images/docs/kubeflow/pipeline-gui-4.png
new file mode 100644
index 00000000..8456186f
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-4.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-5.png b/static/images/docs/kubeflow/pipeline-gui-5.png
new file mode 100644
index 00000000..2464eb5d
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-5.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-6.png b/static/images/docs/kubeflow/pipeline-gui-6.png
new file mode 100644
index 00000000..caf73c0a
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-6.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-7.png b/static/images/docs/kubeflow/pipeline-gui-7.png
new file mode 100644
index 00000000..b0e647f1
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-7.png differ
diff --git a/static/images/docs/kubeflow/pipeline-gui-8.png b/static/images/docs/kubeflow/pipeline-gui-8.png
new file mode 100644
index 00000000..b11f8030
Binary files /dev/null and b/static/images/docs/kubeflow/pipeline-gui-8.png differ
diff --git a/static/images/docs/kubeflow/run-0.png b/static/images/docs/kubeflow/run-0.png
new file mode 100644
index 00000000..6b4139d7
Binary files /dev/null and b/static/images/docs/kubeflow/run-0.png differ
diff --git a/static/images/docs/kubeflow/run-1.png b/static/images/docs/kubeflow/run-1.png
new file mode 100644
index 00000000..b882e70e
Binary files /dev/null and b/static/images/docs/kubeflow/run-1.png differ
diff --git a/static/images/docs/kubeflow/run-10.png b/static/images/docs/kubeflow/run-10.png
new file mode 100644
index 00000000..0733404f
Binary files /dev/null and b/static/images/docs/kubeflow/run-10.png differ
diff --git a/static/images/docs/kubeflow/run-2.png b/static/images/docs/kubeflow/run-2.png
new file mode 100644
index 00000000..c688cc94
Binary files /dev/null and b/static/images/docs/kubeflow/run-2.png differ
diff --git a/static/images/docs/kubeflow/run-3.png b/static/images/docs/kubeflow/run-3.png
new file mode 100644
index 00000000..e7f7a520
Binary files /dev/null and b/static/images/docs/kubeflow/run-3.png differ
diff --git a/static/images/docs/kubeflow/run-4.png b/static/images/docs/kubeflow/run-4.png
new file mode 100644
index 00000000..bd92aeeb
Binary files /dev/null and b/static/images/docs/kubeflow/run-4.png differ
diff --git a/static/images/docs/kubeflow/run-5.png b/static/images/docs/kubeflow/run-5.png
new file mode 100644
index 00000000..6b2a3152
Binary files /dev/null and b/static/images/docs/kubeflow/run-5.png differ
diff --git a/static/images/docs/kubeflow/run-6.png b/static/images/docs/kubeflow/run-6.png
new file mode 100644
index 00000000..e90745a7
Binary files /dev/null and b/static/images/docs/kubeflow/run-6.png differ
diff --git a/static/images/docs/kubeflow/run-7.png b/static/images/docs/kubeflow/run-7.png
new file mode 100644
index 00000000..1fcd4b95
Binary files /dev/null and b/static/images/docs/kubeflow/run-7.png differ
diff --git a/static/images/docs/kubeflow/run-8.png b/static/images/docs/kubeflow/run-8.png
new file mode 100644
index 00000000..5430910a
Binary files /dev/null and b/static/images/docs/kubeflow/run-8.png differ
diff --git a/static/images/docs/kubeflow/run-9.png b/static/images/docs/kubeflow/run-9.png
new file mode 100644
index 00000000..fd56523e
Binary files /dev/null and b/static/images/docs/kubeflow/run-9.png differ