Cloudera AI(구 CML) 워크벤치는 세션·job 이 쓰는 실행 환경을 두 축으로 나눠 관리한다. 하나는 커널·라이브러리를 담은 ML Runtime 이미지이고, 다른 하나는 Spark·Hadoop CLI 같은 무거운 바이너리를 세션에 얹어 주는 Runtime Add-on 이다. 폐쇄망에서 사내 레지스트리(Harbor 등)로 미러링한 이미지를 카탈로그에 등록하거나, PvC Data Services 재설치로 초기화된 애드온을 다시 등록할 때 겪는 문제와 절차를 정리한다. 호스트 이름·자격증명은 예시값으로 바꿔 적었다.
Runtime Catalog → Add Runtime 필드는 docker pull 에 그대로 쓸 수 있는 전체 이미지 경로를 요구한다. 레지스트리 호스트나 태그가 빠지면 latest 를 찾다가 메타데이터 조회에 실패한다.
<레지스트리호스트>[:포트]/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-standard:2023.08.2-b8
UI 에서 Validate 하기 전에 접근 가능한 호스트에서 pull 이 되는지 먼저 검증한다. 레지스트리가 사설 CA·self-signed 인증서를 쓰면 CAI 가 신뢰하지 못해 같은 에러가 나므로 클러스터에 CA 인증서를 추가하고, private 프로젝트면 Site Administration → Runtime → Docker credentials 에 자격증명(로봇 계정 권장)을 먼저 등록한다.
docker pull <레지스트리호스트>:58443/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-standard:2023.08.2-b8
이미지가 정상적으로 읽혀도 Cloudera 공식 런타임은 Add Runtime 버튼(커스텀 런타임 전용 경로)으로 등록되지 않는다. 이미지 내부 라벨 com.cloudera.ml.runtime.edition 값이 Standard 로 박혀 있고 이 값은 예약어이기 때문이다. 검증기는 이미지 경로가 아니라 이미지에 구워진 라벨을 읽으므로 docker tag 로 리포지토리 이름만 바꿔도 라벨은 그대로 따라와 같은 에러가 난다.
먼저 라벨을 확인한다. docker inspect 는 로컬에 pull 된 이미지만 조회하므로 원격만 있으면 먼저 pull 하거나 skopeo inspect 를 쓴다.
docker inspect --format '{{json .Config.Labels}}' \
<레지스트리호스트>:58443/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-standard:2023.08.2-b8 \
| python3 -m json.tool
예약어(Standard / Nvidia GPU / Freshline / Hardened)를 피해 edition 라벨만 바꿔 재빌드한다. 환경변수(ENV)와 라벨(LABEL)을 둘 다 설정해야 한다. editor(Workbench / PBJ Workbench)도 예약어라 edition 통과 후 걸릴 수 있으나, 표시 이름만 바뀌므로 필요할 때만 손댄다. kernel·full-version·short-version 은 그대로 상속시켜 카탈로그에서 원본 버전으로 식별되게 둔다.
FROM <레지스트리호스트>:58443/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-standard:2023.08.2-b8
ENV ML_RUNTIME_EDITION="Legacy"
LABEL com.cloudera.ml.runtime.edition="Legacy"
ENV ML_RUNTIME_DESCRIPTION="Python 3.8 Workbench 2023.08 (test)"
LABEL com.cloudera.ml.runtime.description="Python 3.8 Workbench 2023.08 (test)"
재빌드·푸시한 뒤 새 태그로 Add Runtime 에 등록한다. 커스텀 프로젝트를 public 으로 두면 자격증명 설정을 건너뛸 수 있어 테스트 시 편하다.
정식 런타임을 그대로 쓰려면 라벨을 건드리지 말고 Runtime Repo 파일로 등록한다. 폐쇄망 표준 방법으로, image_identifier 만 사내 레지스트리를 가리키게 하고 나머지 메타데이터는 Cloudera 가 제공하는 repo-assembly.json 의 해당 버전 값을 그대로 복사한다. git_hash·gbn 을 임의로 넣으면 안 된다.
{
"assembly_metadata_version": 1,
"runtimes": [
{
"image_identifier": "<레지스트리호스트>:58443/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-standard:2023.08.2-b8",
"runtime_metadata_version": 2,
"editor": "Workbench",
"edition": "Standard",
"kernel": "Python 3.8",
"full_version": "2023.08.2-b8",
"short_version": "2023.08"
}
]
}
인터넷 되는 PC 에서 https://archive.cloudera.com/ml-runtimes/latest/artifacts/repo-assembly.json 을 받아 해당 항목을 발췌하고 image_identifier 만 사내 주소로 바꾼다. 사내 웹서버에 올린 뒤 Site Administration → Runtime → Runtime Updates 에 URL 을 추가하고 Update Runtimes now 를 누른다.
ML Runtime 이미지에는 JDK·Hadoop 바이너리가 없고 애드온으로 마운트된다. 구버전 이미지(예: Ubuntu 20.04, glibc 2.31)에 최신 워크벤치의 hadoop-cli 애드온(더 최신 OS 기반 JRE)이 붙으면 glibc 세대 차이로 java 자체가 실행되지 않는다. 우회책으로 호환되는 JDK8 을 이미지에 직접 넣을 수 있는데, RPM 으로 설치된 JDK 디렉터리는 tzdb.dat·cacerts·blacklisted.certs 가 호스트 경로를 가리키는 심볼릭 링크라 그대로 복사하면 컨테이너 안에서 깨진다.
find | while read 로 링크를 하나씩 실체화하려 하면 cp -i alias 의 확인 프롬프트가 find 파이프의 stdin 을 삼켜 루프가 첫 항목에서 멈춘다. 링크를 쫓지 말고 cp -aL 로 전부 실체화해 재복사하는 편이 확실하다.
JDK=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.402.b06-2.el8.x86_64
rm -rf ~/rt-test/java8 && mkdir -p ~/rt-test/java8
cp -aL "$JDK/." ~/rt-test/java8/
find ~/rt-test/java8 -type l # 아무것도 안 나와야 정상
Dockerfile 에서 이 디렉터리를 복사하고 JAVA_HOME 을 잡는다.
FROM <레지스트리호스트>:58443/<프로젝트>/cloudera/cdsw/ml-runtime-workbench-python3.8-test:2023.08.2-b8-legacy
USER root
COPY java8 /opt/java8
RUN chmod -R a+rX /opt/java8 && chmod a+rx /opt/java8/bin/* /opt/java8/jre/bin/*
ENV JAVA_HOME=/opt/java8
ENV PATH=/opt/java8/bin:$PATH
USER cdsw
빌드 전에 마운트만으로 실행되는지 검증한다. tzdb.dat 에러 없이 버전만 떠야 한다.
docker run --rm -v $PWD/java8:/opt/java8:ro \
--entrypoint /opt/java8/bin/java <이미지> -version
애드온이 세션 시작 시 JAVA_HOME 을 덮어쓰므로 이미지 ENV 만으로는 부족하다. Project Settings → Advanced → Environment Variables 에 JAVA_HOME=/opt/java8 을 넣고 세션을 재시작해야 hdfs 명령이 옵션 없이 동작한다.
PvC Data Services 를 삭제 후 재설치하면 애드온 등록이 초기화되어 세션에서 hdfs·spark 명령이 사라질 수 있다. 1.5.5 부터 관리자가 JSON 형식의 add-on repository 파일을 업로드하면 API 가 검증한 뒤 비동기로 애드온을 로딩한다.
User Settings → API Keys 에서 APIv2 키 발급https://archive.cloudera.com/ml-runtime-addons/pvc/<버전>/artifacts/repo-assembly.json 을 미리 내려받아 내부에 둔다https://<워크벤치URL>/api/v2/swagger.html 의 runtimeaddons 섹션에서 정확한 엔드포인트를 확인해 업로드Site Administration → Runtime/Engine 탭에서 Hadoop CLI / Spark 버전이 뜨는지 확인하고 기본값 선택JDK 등 별도 바이너리를 애드온으로 넣을 때 쓴다. 마운트할 경로 구조 그대로 압축하고 metadata.json 을 함께 올린다.
{
"name": "my-java11-addon",
"spec": { "spec": { "paths": ["/usr/custom_file", "/usr/bin/custom_folder"] } }
}
export CML_URL="https://<워크벤치도메인>"
read -s APIV2_KEY # 키를 명령행·로그에 남기지 않기 위해 입력받는다
curl -v -L "${CML_URL}/api/v2/runtimeaddons/custom" \
-F metadata=@metadata.json \
-F tarball=@addon.tar.gz \
-H "Authorization: Bearer ${APIV2_KEY}"
제약을 지킨다. 이름은 소문자·숫자·언더스코어·하이픈만, 35자 이내이며 등록 시 custom-addon- 접두어가 붙는다. 경로는 최소 1개, 끝에 / 를 붙이지 않는다. 요청 경로는 cdsw 유저가 심볼릭 링크로 만들기 때문에 워크로드 파일시스템에 이미 존재하면 안 되고 cdsw 유저에게 쓰기 권한이 있어야 한다. 커스텀 애드온은 UI 로 추가할 수 없고 등록 후 수정·삭제가 불가능하며 비활성화만 된다. read-only 로 마운트되고 Python/R 라이브러리 주입 용도로는 못 쓴다 — 그 경우 Custom Runtime 을 만든다.