참고
pipeline SDK로 kubeflow piplines 서버에 연결하는 방법은 kubeflow가 어떻게 구성되어 있냐에 따라 다른데 여기서는 full kubeflow 구성만 전제하자. 그리고 클러스터 내부/외부에 따라 인증/인가 방법이 다른데 이에 대해 각각 알아보자.
그전에 Pipeline SDK가 pipeline API 서버와 통신 방법을 알아보자.
사용자는 kfp.Client()
클래스를 사용해 pipeline API와 연결함. 이 클래스에는 주요한 인자 2 가지가 아래와 같이 있음.
host
: 연결하고자 하는 pipeline API의 hostname 엔드포인트. 따로 설정하지 않으면 클러스터 내에 파드인 경우에만 작동하는 service DNS name이 사용됨. credentials
: pipeline API 서버에 대해 인증하기 위한 자격 증명 TokenCredentialsBase 객체. ServiceAccount 토큰 경로를 path 인자로 받아 활용함.먼저, full kubeflow로 설치했을 때 클러스터 내부에서(예를 들어, kubeflow notebook 인스턴스 파드에서) pipeline 서버로 연결하는 방법을 알아보자. pipeline API 서버는 kubeflow
네임스페이스에 ml-pipeline
deployment로 띄워져 있고 이에 대한 service는 ml-pipline
으로 8888
포트를 포트포워딩 해주고 있음. 그러므로 클러스터 내부에서 다른 파드에 의해 pipeline API로 접근하려면 ml-pipeline.kubeflow.svc.cluster.local:8888
로 호출하면 됨. 이 값은 앞서 봤던 kfp.Client()
의 host
인자 기본값과 똑같음. 즉, pipeline API 엔드포인트는 기본 설정이 되어있다는 말임.
한편, k8s에서는 Pod를 생성할 때, ServiceAccount token이 volume으로 마운트되므로 인증 수단으로 활용되곤 함. 이와 관련해서 kubeflow에서는 사용자가 notebook 인스턴스를 만들면 해당 사용자 네임스페이스의 default-editor
라는 ServiceAccount token이 마운트됨. 기본적으로 경로는 /var/run/secrets/kubernetes.io/serviceaccount/token
에 마운트 됨. 이 토큰을 kubeflow pipeline API에 인증으로 사용할 수 있음.
단, ServiceAccount token을 인증 수단으로 활용하더라도 적절한 권한이 있는 인가를 하지 못하면 아래와 같이 RBAC access denied 에러가 발생함.
import kfp
host_url = "http://ml-pipeline.kubeflow.svc.cluster.local:8888"
credentials = kfp.auth.ServiceAccountTokenVolumeCredentials(path='/var/run/secrets/kubernetes.io/serviceaccount/token')
client = kfp.Client(host=host_url, credentials=credentials)
client.__dict__
client.get_kfp_healthz()
그러므로 이제 해당 ServiceAccount에게 적절한 권한을 부여하자.
pipeline-rbac-authorizationPolicy.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: bind-ml-pipeline-nb-kubeflow-kubeflow-jbpark8
namespace: kubeflow
spec:
selector:
matchLabels:
app: ml-pipeline
rules:
- from:
- source:
principals: ["cluster.local/ns/kubeflow-jbpark8/sa/default-editor"]
kubeflow
네임스페이스에 있는 pipeline api 서버가 있는 파드에 대한 권한임.kubeflow-jbpark8
네임스페이스에 있는 default-editor
ServiceAccount에게 kubeflow
네임스페이스에서 app: ml-pipeline
label이 있는 파드들에 대한 모든(?) 권한을 부여함.다시 커넥션 테스트를 해보면 잘 됨.
여기까지만 해도 연결이 잘 되지만 로직을 좀더 깔끔하게 해보자.
위에서 credentials 변수를 만들 때 kfp.auth.ServiceAccountTokenVolumeCredentials()
함수를 사용했는데 여기에 path 인자에 대한 인수로 기본 serviceAccount에 대한 token 경로 ‘/var/run/secrets/kubernetes.io/serviceaccount/token’
를 추가해줬었음. 이는 번거로울 수 있으므로 따로 설정하지 않는 방법을 적용하자. 그러니까 앞서 봤던 path 인자의 기본값을 활용해보자.
path 인자의 기본값을 확인해보면 아래와 같음.
즉, path 인자는 KF_PIPELINES_SA_TOKEN_ENV
또는 KF_PIPELINES_SA_TOKEN_PATH
환경 변수를 읽어온다. 그러므로 우리는 이 환경 변수에 토큰이 저장되는 경로를 설정하거나 또는 이 환경 변수의 기본 경로에 토큰을 저장하면 됨.
더 자세히 보자. 이 변수들은 코드 상에서 아래와 같이 /var/run/secrets/kubeflow/pipelines/token
로 설정되어 있음. 즉, 우리는 이 경로에 token을 마운트해주면 쉽게 해결됨.
그런데, 최소 권한 원칙을 고려하여 pipeline에 대한 권한만 부여하는게 좋음. 그러므로 token의 인증 범위를 설정하자. audience에 대해서 pipelines.kubeflow.org
을 지정해주면 됨. 그런데 이를 위해서는 기본 token을 제한하면 또 다른 곳에서 문제가 생길 수 있므로 기본 token을 사용하지 않고 같은 default-editor 서비스어카운트를 사용하되 별도로 audience 설정을 한 token을 앞서 본 path 기본 경로에 마운트 해주면 됨.
다시 말해, 개선 사항으로는 아래 두 가지가 있음.
결국, 이 두 가지 작업은 pod에 환경변수를 추가하고 serviceAccount token을 마운트하는 걸로 수행할 수 있는데 이는 podDefault를 활용하는게 좋음. podDefault를 사용하면 kubeflow에서 notebook 인스턴스를 생성할 때, Configurations 에서 podDefault를 선택할 수 있음. 선택된 podDefault는 정의된 내용에 따라 notebook 인스턴스로 생성될 pod의 manifest를 수정해줌.
pipeline-rbac-podDefault.yaml
apiVersion: kubeflow.org/v1alpha1
kind: PodDefault
metadata:
name: access-ml-pipeline
namespace: "kubeflow-jbpark8"
spec:
desc: Allow access to Kubeflow Pipelines
selector:
matchLabels:
access-ml-pipeline: "true"
env:
- ## this environment variable is automatically read by `kfp.Client()`
## this is the default value, but we show it here for clarity
name: KF_PIPELINES_SA_TOKEN_PATH
value: /var/run/secrets/kubeflow/pipelines/token
volumes:
- name: volume-kf-pipeline-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 7200
## defined by the `TOKEN_REVIEW_AUDIENCE` environment variable on the `ml-pipeline` deployment
audience: pipelines.kubeflow.org
volumeMounts:
- mountPath: /var/run/secrets/kubeflow/pipelines
name: volume-kf-pipeline-token
readOnly: true
pipelines.kubeflow.org
로 지정하여 pipeline api 관련 인가만 진행할 수 있도록 제한했으며KF_PIPELINES_SA_TOKEN_PATH
환경변수의 값을 /var/run/secrets/kubeflow/pipelines/token
으로 지정했고/var/run/secrets/kubeflow/pipelines
에 마운트 시켰음.이제 다시 연결을 해보자.
import kfp
#host_url = "http://ml-pipeline.kubeflow.svc.cluster.local:8888"
# the KF_PIPELINES_SA_TOKEN_PATH environment variable is used when no `path` is set
# the default KF_PIPELINES_SA_TOKEN_PATH is /var/run/secrets/kubeflow/pipelines/token
#credentials = kfp.auth.ServiceAccountTokenVolumeCredentials(path=None)
#client = kfp.Client(host=host_url, credentials=credentials)
client = kfp.Client()
Kubeflow는 인증/인가를 통해 Kubeflow Pipelines 퍼블릭 엔드포인트를 보호함. 그러므로 multi-user 모드의 Kubeflow Pipelines의 경우 인증이 필요하기 때문에 kubectl port-forward
를 사용하여 API에 액세스할 수 없음. 또한 Kubeflow 배포판에 따라 인증/인가 요구 사항이 다를 수 있으므로 각 배포판 마다 권고되는 방법을 따르기 위해 각 문서들을 참조하자. 나는 Amazon EKS 위에 올렸으므로 [kubeflow on aws docs : Pipelines] 를 참고했음.
먼저, chrome 브라우저를 열어서 Kubeflow에 로그인한 후 쿠키를 복사하자.
쿠키 예시
클러스터 밖 환경에서 pipeline 연결 예제
import kfp
kubeflow_gateway_endpoint = "internal-k8s-istiosys-istioing-****.ap-northeast-2.elb.amazonaws.com"
authservice_session_cookie = "MTY4MDgyNzkyMH*****"
namespace='kubeflow-jbpark8'
client = kfp.Client(host=f"https://{kubeflow_gateway_endpoint}/pipeline", cookies=f"authservice_session={authservice_session_cookie}")
client.list_experiments(namespace=namespace)