@ -311,7 +311,9 @@ public class AdminService {
if ( runId ! = null & & ! runId . isBlank ( )
& & podNameParam ! = null & & ! podNameParam . isBlank ( )
& & kubeflowUrl ! = null & & ! kubeflowUrl . isBlank ( ) ) {
String kfpOnly = tryKfpMlPipelineNodeLog ( runId , podNameParam . trim ( ) ) ;
String ns = ( workflowNamespaceParam ! = null & & ! workflowNamespaceParam . isBlank ( ) )
? workflowNamespaceParam . trim ( ) : null ;
String kfpOnly = tryKfpMlPipelineNodeLog ( runId , ns , podNameParam . trim ( ) ) ;
if ( kfpOnly ! = null ) {
return "-- KFP ml-pipeline API (kubectl 없이) v1beta1 노드 로그 --\n\n" + kfpOnly ;
}
@ -347,7 +349,7 @@ public class AdminService {
}
}
if ( preferKfpApiForLogs ) {
String kfpFirst = tryKfpMlPipelineNodeLog ( runId , res0 . podName, podNameParam . trim ( ) ) ;
String kfpFirst = tryKfpMlPipelineNodeLog ( runId , res0 . namespace, res0 . podName, podNameParam . trim ( ) ) ;
if ( kfpFirst ! = null ) {
return "-- KFP ml-pipeline API (UI와 동일) | node: " + res0 . podName
+ ( podNameParam . trim ( ) . equals ( res0 . podName ) ? "" : " (요청: " + podNameParam . trim ( ) + ")" )
@ -363,6 +365,32 @@ public class AdminService {
podHealthService . readPipelinePodLog ( res . podName , null , normalizeTail ( tailLines ) ) ;
logText = fb . logText ;
}
if ( logText ! = null & & logText . contains ( "code=403" ) & & logText . contains ( "ApiException" ) ) {
log . warn ( "[Admin] K8s Pod 로그 조회 권한이 없습니다 (403 Forbidden). Kubeflow API로 대체를 시도합니다. runId={}, podName={}" , runId , podNameParam ) ;
String resolvedTaskId = null ;
try {
Map < String , Object > run = pipelineUploadService . getKfpRunById ( runId ) ;
List < Map < String , Object > > tasks = kfpTasksWithPods ( run ) ;
for ( Map < String , Object > t : tasks ) {
String pod = firstString ( t . get ( "podName" ) , t . get ( "pod_name" ) ) ;
if ( podNameParam . trim ( ) . equals ( pod ) | | res . podName . equals ( pod ) ) {
resolvedTaskId = firstString ( t . get ( "taskId" ) , t . get ( "task_id" ) ) ;
if ( resolvedTaskId ! = null & & ! resolvedTaskId . isBlank ( ) ) {
break ;
}
}
}
} catch ( Exception ex ) {
log . error ( "[Admin] KFP Run에서 Task ID 추출 실패: {}" , ex . getMessage ( ) ) ;
}
String kfpLog = tryKfpMlPipelineNodeLog ( runId , res . namespace , resolvedTaskId , res . podName , podNameParam . trim ( ) ) ;
if ( kfpLog ! = null ) {
log . info ( "[Admin] Kubeflow API로 로그 대체 조회 성공. nodeId/podName={}" , resolvedTaskId ! = null ? resolvedTaskId : res . podName ) ;
return "-- KFP ml-pipeline API (kubectl 403 Forbidden 대체) --\n\n" + kfpLog ;
} else {
log . warn ( "[Admin] Kubeflow API로 로그 대체 조회 실패. runId={}, podName={}, resolvedTaskId={}" , runId , podNameParam , resolvedTaskId ) ;
}
}
String out = "-- kubectl logs " + res . podName + " -n " + res . namespace
+ ( podNameParam . trim ( ) . equals ( res . podName ) ? "" : " (요청: " + podNameParam . trim ( ) + ")" )
+ " --\n\n" + ( logText ! = null ? logText : "" ) ;
@ -468,7 +496,7 @@ public class AdminService {
}
}
if ( preferKfpApiForLogs ) {
String kfpLog = tryKfpMlPipelineNodeLog ( runId , res. podName , pod . trim ( ) ) ;
String kfpLog = tryKfpMlPipelineNodeLog ( runId , wfNsEff, res. podName , pod . trim ( ) ) ;
if ( kfpLog ! = null ) {
StringBuilder k = new StringBuilder ( ) ;
k . append ( "-- KFP ml-pipeline API v1beta1/runs/.../nodes/{node_id}/log (UI와 동일 백엔드) --\n" ) ;
@ -488,6 +516,25 @@ public class AdminService {
podHealthService . readPipelinePodLog ( res . podName , null , normalizeTail ( tailLines ) ) ;
logText = fb . logText ;
}
if ( logText ! = null & & logText . contains ( "code=403" ) & & logText . contains ( "ApiException" ) ) {
log . warn ( "[Admin] K8s Pod 로그 조회 권한이 없습니다 (403 Forbidden). Kubeflow API로 대체를 시도합니다. runId={}, podName={}, step={}" , runId , res . podName , step ) ;
String taskId = firstString ( task . get ( "taskId" ) , task . get ( "task_id" ) ) ;
String kfpLog = tryKfpMlPipelineNodeLog ( runId , res . namespace , taskId , res . podName , pod . trim ( ) ) ;
if ( kfpLog ! = null ) {
log . info ( "[Admin] Kubeflow API로 로그 대체 조회 성공. nodeId/podName={}" , taskId ! = null ? taskId : res . podName ) ;
StringBuilder k = new StringBuilder ( ) ;
k . append ( "-- KFP ml-pipeline API (kubectl 403 Forbidden 대체) --\n" ) ;
k . append ( "node_id: " ) . append ( taskId ! = null ? taskId : res . podName ) ;
if ( ! pod . trim ( ) . equals ( res . podName ) ) {
k . append ( " | KFP task pod_name: " ) . append ( pod . trim ( ) ) ;
}
k . append ( " | Step: " ) . append ( step ! = null ? step : "(이름 없음)" ) . append ( " --\n\n" ) ;
k . append ( kfpLog ) ;
return k . toString ( ) ;
} else {
log . warn ( "[Admin] Kubeflow API로 로그 대체 조회 실패. runId={}, podName={}, taskId={}" , runId , res . podName , taskId ) ;
}
}
StringBuilder sb = new StringBuilder ( ) ;
sb . append ( "-- kubectl logs " ) . append ( res . podName ) . append ( " -n " ) . append ( res . namespace ) ;
sb . append ( " (KFP 로그와 동일) | Step: " ) . append ( step ! = null ? step : "(이름 없음)" ) ;
@ -506,7 +553,7 @@ public class AdminService {
/ * *
* KFP UI 가 쓰 는 것 과 같 은 ml - pipeline 노 드 로 그 . node_id 후 보 순 서 대 로 시 도 .
* /
private String tryKfpMlPipelineNodeLog ( String runId , String . . . nodeIdsOrdered ) {
private String tryKfpMlPipelineNodeLog ( String runId , String namespace , String . . . nodeIdsOrdered ) {
if ( runId = = null | | runId . isBlank ( ) | | kubeflowUrl = = null | | kubeflowUrl . isBlank ( ) ) {
return null ;
}
@ -519,7 +566,7 @@ public class AdminService {
if ( ! seen . add ( t ) ) {
continue ;
}
String body = pipelineUploadService . getV1beta1RunNodeLog ( runId , t );
String body = pipelineUploadService . getV1beta1RunNodeLog ( runId , t , namespace );
if ( isSubstantialKfpV1LogBody ( body ) ) {
return body ;
}