このドキュメントでは、Google Distributed Cloud(GDC)エアギャップ環境にオープンウェイト モデルをデプロイする手順について説明します。Kubernetes、Harbor、アクセラレータ(GPU)リソースなど、GDC プラットフォームの機能を使用して、高スループットのサービングに vLLM を使用する方法について説明します。
アーキテクチャ
このソリューションでは、標準クラスタ内にコンテナ化された推論サービング バックエンド(Deployment)として vLLM をデプロイします。モデルの重みは、ダウンロードされたモデル ディレクトリから動的にサイズ設定された永続ボリュームに保存されます。このボリュームには 25% のオーバーヘッドがあり、system-performance-<org>-admin ストレージ QoS ポリシー(1 GiB あたり 30 IOPS、最小 6,148 IOPS、または高パフォーマンス ブロック ストレージが有効になっている場合は performance-rwo)で構成され、モデルローダー ワークフローを使用して入力されます。LoadBalancer タイプの Kubernetes Service は、バックエンド API を公開し、NetworkPolicy / ProjectNetworkPolicy オブジェクトは、それへのアクセスを保護します。

始める前に
デプロイに進む前に、環境が必要な前提条件をすべて満たしていること、必要なコマンドライン ユーティリティが適切に構成されていることを確認してください。ワークステーションにこれらのツールを設定することは、Container Registry の管理、クラスタとのやり取り、デプロイ プロセスの自動化に不可欠です。
- 十分なリソース(CPU、メモリ、アクセラレータ)を使用して作成された Standard クラスタ。アクセラレータの最小要件はモデルによって異なります。
- Harbor インスタンスが利用可能で、アクセスできる。
- 必要な IAM が適用されている。
- 環境とインターネットへの必要な接続性を備えたワークステーション
- ゲート付きモデルを使用する場合は、Hugging Face トークンと利用規約に同意している。
環境の構成
Identity and Access Management
必要な IAM アカウント、ロール、権限が正しく構成されていることを確認します。
組織に対する GDC ユーザーロール:
- 省略可: 標準クラスタ管理者(コンソールからクラスタを表示する)
プロジェクトに対する GDC ユーザーロール(プロジェクト IAM 管理者によって付与される、プロジェクト Namespace の RoleBinding):
- Harbor インスタンス閲覧者(
harbor-instance-viewer) - Harbor プロジェクト作成者(
harbor-project-creator、Harbor プロジェクトがまだ存在しない場合のみ) - 標準クラスタ管理者(
standard-cluster-admin、gdcloud clusters get-credentialsに必要)
標準クラスタに対する GDC ユーザーロール: 前述のプロジェクト ロールでは、クラスタ内の権限は付与されません。プロジェクト IAM 管理者は、管理 API サーバーのプロジェクト名前空間で StandardClusterRoleBinding を使用して、ユーザーを StandardClusterRole cluster-admin にバインドする必要があります。バインドは数秒以内にプロジェクトの標準クラスタに伝播されます(status.clusters[].conditions に Propagated=True が表示されます)。
cat <<EOF | kubectl apply --kubeconfig MANAGEMENT_API_SERVER -f -
apiVersion: iam.gdc.goog/v1
kind: StandardClusterRoleBinding
metadata:
name: BINDING_NAME
namespace: PROJECT
spec:
roleRef:
apiGroup: iam.gdc.goog
kind: StandardClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: USER
EOF
次のように置き換えます。
MANAGEMENT_API_SERVER: 管理 API サーバーの kubeconfig ファイルへのパス。BINDING_NAME: バインディングの名前(alex-cluster-adminなど、英小文字、数字、ハイフン)。PROJECT: プロジェクトの名前。USER: バインドするユーザー名。ID プロバイダが報告するユーザー名(メールアドレスなど)と完全に一致する必要があります。
インフラストラクチャ オペレーターの支援(標準クラスタで performance-rwo が使用できない場合のみ): ユーザーガイドでは、デフォルトの standard-rwo StorageClass を使用してモデル PersistentVolumeClaim を作成します。これにより、適切なサイズのモデル ボリュームが GiB あたり 3 IOPS に制限されます。インフラストラクチャ オペレーターに、モデル PersistentVolumeClaim をバックアップするボリュームに system-performance-<ORG>-admin QoS ポリシーを適用して、6,148 IOPS の下限を受け取るように依頼します。ユーザーガイドには、インフラストラクチャ オペレーターが必要とする値が記載されています。
crane の Harbor ロボット アカウントの権限:
- リポジトリを一覧表示する
- リポジトリを pull する
- リポジトリを push する
- アーティファクトを読み取る
- アーティファクトを一覧表示する
- タグを作成
- リストタグ
Kubernetes イメージの pull に対する Harbor ロボット アカウントの権限:
- リポジトリを一覧表示する
- リポジトリを pull する
- アーティファクトを読み取る
- アーティファクトを一覧表示する
- リストタグ
ワークステーション
特に記載がない限り、このガイドのすべてのコマンドは、必要なツールと環境およびインターネットへの接続を備えたワークステーションから実行します。
要件
ワークステーションに次のツールをインストールする必要があります。
crane: レジストリ間でコンテナ イメージを管理、コピーします(ドキュメント)。docker: パッチを適用した vLLM コンテナ イメージをビルドして push します。gdcloud: GDC リソースを管理するためのコマンドライン インターフェース(CLI)(ドキュメント)。hf: Hugging Face へのアクセスに使用されるコマンドライン インターフェース(CLI)。kubectl: Kubernetes クラスタとの通信と管理に使用されるコマンドライン インターフェース(CLI)。jq: 軽量で柔軟なコマンドライン JSON プロセッサ。yq: ポータブルなコマンドライン YAML プロセッサ。
ワークステーションの構成
ワークステーションの構成には、環境に関する次の情報が必要です。
GDC_STANDARD_CLUSTER_NAME: GDC 標準クラスタの名前。GDC_DOMAIN_SUFFIX: GDC 環境のドメイン サフィックス(例:gdc.example.com)。GDC_ORG: GDC 組織の名前。GDC_PROJECT: GDC プロジェクトの名前。GDC_ZONE: GDC デプロイ ゾーンの名前。GDC_HARBOR_INSTANCE_NAME: プロジェクト内の Harbor インスタンスの名前。GDCS_HARBOR_PROJECT_NAME: イメージに使用する Harbor プロジェクトの名前(デフォルト:solutions)。GDCS_HARBOR_CRANE_ROBOT_NAME: Harborcraneロボット アカウントの名前。GDCS_HARBOR_CRANE_ROBOT_TOKEN: Harborcraneロボット アカウントの認証トークン。GDCS_HARBOR_K8S_ROBOT_NAME: Harbor Kubernetes イメージ pull ロボット アカウントの名前。GDCS_HARBOR_K8S_ROBOT_TOKEN: Harbor Kubernetes イメージ pull ロボット アカウントの認証トークン。
必要なすべての変数の値を収集したら、環境変数ファイルの生成に進みます。作成後は、いつでもファイルを手動で編集できます。
ルート ソリューション ディレクトリと secrets フォルダを作成します。
mkdir -p ${HOME}/gdcag-solutions/env.d mkdir -p ${HOME}/gdcag-solutions/secrets touch ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token touch ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token chmod u=rwx,go= ${HOME}/gdcag-solutions/secrets chmod -R u=rw,go= ${HOME}/gdcag-solutions/secrets/*プラットフォーム環境構成ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/platform.sh && echo "Successfully created." || echo "Failed to create!" # Infrastructure (Platform Native) export GDC_STANDARD_CLUSTER_NAME="STANDARD_CLUSTER_NAME" export GDC_DOMAIN_SUFFIX="DOMAIN_SUFFIX" export GDC_ORG="ORG" export GDC_PROJECT="PROJECT" export GDC_ZONE="ZONE" export GDC_HARBOR_INSTANCE_NAME="HARBOR_INSTANCE_NAME" # Derived platform values export GDC_ZONAL_HOSTNAME="${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}" export GDC_ZONAL_CONSOLE_URL="https://console.${GDC_ZONAL_HOSTNAME}" export GDC_HARBOR_HOST="${GDC_HARBOR_INSTANCE_NAME}-${GDC_PROJECT}.${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}" EOF次のように置き換えます。
STANDARD_CLUSTER_NAME: GDC 標準クラスタ名。DOMAIN_SUFFIX: GDC ドメイン サフィックス。ORG: GDC 組織。PROJECT: GDC プロジェクト。ZONE: GDC ゾーン。HARBOR_INSTANCE_NAME: GDC Harbor インスタンス名。
レジストリ環境の構成ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/registry.sh && echo "Successfully created." || echo "Failed to create!" # GDC Solutions Registry & Secrets export GDCS_HARBOR_PROJECT_NAME="solutions" export GDCS_HARBOR_CRANE_ROBOT_NAME='HARBOR_CRANE_ROBOT_NAME' export GDCS_HARBOR_CRANE_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_crane_robot_token)" export GDCS_HARBOR_K8S_ROBOT_NAME='HARBOR_K8S_ROBOT_NAME' export GDCS_HARBOR_K8S_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_k8s_robot_token)" export GDCS_HARBOR_K8S_PULL_SECRET="gdcs-image-pull-secret" # Derived registry values export GDCS_HARBOR_PROJECT_URI="${GDC_HARBOR_HOST}/${GDCS_HARBOR_PROJECT_NAME}" export GDCS_HARBOR_CHART_OCI_URI="oci://${GDCS_HARBOR_PROJECT_URI}" EOF次のように置き換えます。
HARBOR_CRANE_ROBOT_NAME:craneの Harbor ロボット アカウントの完全な名前。Harbor に表示されるとおりに指定します。robot$接頭辞も含まれます。シェルが$を展開しないように、一重引用符をそのままにします。HARBOR_K8S_ROBOT_NAME: Kubernetes イメージの pull 用の Harbor ロボット アカウントのフルネーム。Harbor に表示されるとおり、robot$接頭辞を含みます。シェルが$を展開しないように、一重引用符をそのままにします。
シークレット ファイルにトークンを追加します。
set +o history echo "CRANE_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token echo "KUBERNETES_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token set -o history次のように置き換えます。
CRANE_ROBOT_TOKEN:craneロボット アカウントのシークレット。KUBERNETES_ROBOT_TOKEN: Kubernetes イメージ pull ロボット アカウントのシークレット。
ルート環境ローダー ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/env.sh && echo "Successfully created." || echo "Failed to create!" export GDCS_ROOT_HOME="${HOME}/gdcag-solutions" echo "GDCS_ROOT_HOME=${GDCS_ROOT_HOME}" # Sourced in dependency order source "${GDCS_ROOT_HOME}/env.d/platform.sh" source "${GDCS_ROOT_HOME}/env.d/registry.sh" EOF
ソリューション変数を構成する
ソリューション実装ディレクトリと secrets フォルダを作成します。
mkdir -p ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d mkdir -p ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets touch ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/huggingface_token chmod u=rwx,go= ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets chmod -R u=rw,go= ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/*Hugging Face トークンを Secret ファイルに追加します。
set +o history echo "HUGGINGFACE_TOKEN" > ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/huggingface_token set -o history次のように置き換えます。
HUGGINGFACE_TOKEN: Hugging Face アクセス トークン。
アクセラレータ構成ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/accelerator.sh && echo "Successfully created." || echo "Failed to create!" # Workload Accelerator (NVIDIA GPU) export GDCS_WORKLOAD_ACCELERATOR_TYPE="WORKLOAD_ACCELERATOR_TYPE" export GDCS_WORKLOAD_ACCELERATOR_COUNT="1" EOF次のように置き換えます。
WORKLOAD_ACCELERATOR_TYPE: GPU ノードが公開する GPU リソース名(例:nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE)。ユーザーガイドには、GDC エアギャップが提供するすべての GPU のリソース名が記載されています。クラスタの認証情報を取得したら、kubectl get nodes --output=json | jq -r '[.items[].status.allocatable | keys[] | select(startswith("nvidia.com/"))] | unique[]'を使用してクラスタ内の名前を一覧表示できます。
ソリューション環境構成ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/vllm.sh && echo "Successfully created." || echo "Failed to create!" # Hugging Face export HF_TOKEN="$(cat ${GDCS_IMPLEMENTATION_HOME}/secrets/huggingface_token 2>/dev/null || echo '')" # Images export GDCS_BUSYBOX_IMAGE_TAG="1.38.0" export GDCS_VLLM_IMAGE_TAG="v0.26.0" # Docker configuration directories for crane and Kubernetes export GDCS_HARBOR_CRANE_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/crane" export GDCS_HARBOR_K8S_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/k8s" EOF実装環境ローダー ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.sh && echo "Successfully created." || echo "Failed to create!" source "${HOME}/gdcag-solutions/env.sh" export GDCS_IMPLEMENTATION_HOME="${HOME}/gdcag-solutions/open-weight-models/vllm" echo "GDCS_IMPLEMENTATION_HOME=${GDCS_IMPLEMENTATION_HOME}" # Sourced in dependency order source "${GDCS_IMPLEMENTATION_HOME}/env.d/accelerator.sh" source "${GDCS_IMPLEMENTATION_HOME}/env.d/vllm.sh" EOF任意のテキスト エディタで環境ファイルを編集して確認します。
${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/platform.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/registry.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/accelerator.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/vllm.sh環境ファイルを source コマンドで実行します。
source ${HOME}/gdcag-solutions/open-weight-models/vllm/env.sh出力は次のようになります。
GDCS_ROOT_HOME=HOME_DIRECTORY_PATH/gdcag-solutions GDCS_IMPLEMENTATION_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/open-weight-models/vllm
GDC
このガイドでは、GDC 環境と Harbor インスタンスの TLS 証明書を信頼するようにワークステーションが構成されていることを前提としています。
gdcloudを構成します。gdcloud config set core/account "default-user" gdcloud config set core/organization_console_url "${GDC_ZONAL_CONSOLE_URL}" gdcloud config set core/project "${GDC_PROJECT}" gdcloud config set core/zone "${GDC_ZONE}"GDC 環境に対して認証します。
gdcloud auth login
クラスタ
クラスタの認証情報を取得します。
gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \ --project="${GDC_PROJECT}" \ --standard \ --zone="${GDC_ZONE}"クラスタへの接続性を確認します。
kubectl get nodes -L node.cluster.private.gdc.goog/machine-class
アーティファクトの移行の準備
- Artifact Registry の接続性と構成を確認します。
crane の Docker 構成ファイルを作成します。ロボット アカウントは、ユーザー アカウントで Managed Harbor Service(MHS)認証情報ヘルパー(docker-credential-mhs)を使用するときに、認証トークンのタイムアウトを回避するために、大きなイメージレイヤを push するために使用されます。
export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" crane auth login "${GDC_HARBOR_HOST}" \ --password="${GDCS_HARBOR_CRANE_ROBOT_TOKEN}" \ --username="${GDCS_HARBOR_CRANE_ROBOT_NAME}"Kubernetes 用の Docker 構成ファイルを作成します。
export DOCKER_CONFIG="${GDCS_HARBOR_K8S_DOCKER_CONFIG}" crane auth login "${GDC_HARBOR_HOST}" \ --password="${GDCS_HARBOR_K8S_ROBOT_TOKEN}" \ --username="${GDCS_HARBOR_K8S_ROBOT_NAME}"seed_registry.shスクリプトを作成します。cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh && echo "Successfully created." || echo "Failed to create!" #!/bin/bash # seed_registry.sh: Modular artifact migration for GDC Solutions # Requires the env.sh file to be sourced first. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o nounset source "${SCRIPT_DIR}/env.sh" # Set the Docker config export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" eval "${SERIALIZED_IMAGES}" # Ensure GDCS_REGISTRY_IMAGES is set if [[ ${#GDCS_REGISTRY_IMAGES[@]} -eq 0 ]]; then echo "GDCS_REGISTRY_IMAGES must be set, exiting..." exit 1 fi # Migrate the images for source_image in "${GDCS_REGISTRY_IMAGES[@]}"; do # Strip the registry host only when the first path segment is a host (contains a dot or a port) first_segment="${source_image%%/*}" if [[ "${first_segment}" == *.* || "${first_segment}" == *:* ]]; then image_path="${source_image#*/}" else image_path="${source_image}" fi destination_image="${GDCS_HARBOR_PROJECT_URI}/${image_path}" # Ensure the folder structure is created crane append \ --new_layer=<(tar czf - -T /dev/null) \ --new_tag="${destination_image%:*}:create" \ --oci-empty-base 2> /dev/null || true # Copy the linux/amd64 platform only to avoid transferring multi-arch layers over air-gapped links crane copy --platform linux/amd64 "${source_image}" "${destination_image}" || { echo "Failed to copy ${source_image} to ${destination_image}"; exit 1; } done echo "Migration complete: Images are available at ${GDCS_HARBOR_PROJECT_URI}" EOF chmod u+x "${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh"ソリューションに必要なコンテナ イメージのリストを定義します。
declare -a GDCS_REGISTRY_IMAGES=( "docker.io/library/busybox:${GDCS_BUSYBOX_IMAGE_TAG}" "docker.io/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)必要なコンテナ イメージを Artifact Registry にシードします。
${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
アップストリームの変更が実装されるまで、vLLM コンテナにパッチを適用する Dockerfile を作成します。
cat <<EOF > ${GDCS_IMPLEMENTATION_HOME}/docker/vllm-${GDCS_VLLM_IMAGE_TAG}-patch.dockerfile && echo "Successfully created." || echo "Failed to create!" FROM docker.io/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG} RUN pip install --upgrade "opencv-python-headless<=4.12.0.88" EOFパッチを適用したイメージをビルドして push します。
context_dir="${GDCS_IMPLEMENTATION_HOME}/docker/empty-context" image="${GDCS_HARBOR_PROJECT_URI}/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG}-fips" mkdir -p "${context_dir}" rm -rf "${context_dir}"/* export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" docker build \ --file="${GDCS_IMPLEMENTATION_HOME}/docker/vllm-${GDCS_VLLM_IMAGE_TAG}-patch.dockerfile" \ --tag="${image}" \ "${context_dir}" docker push "${image}"model_weight_upload.shスクリプトを作成します。モデルローダーPod(GNUtarストリーミング、8 GiB を超えるファイルを含む)を介してローカル モデル ディレクトリをPersistentVolumeにコピーし、同じサイズですでに存在するファイルをスキップします。cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/model_weight_upload.sh && echo "Successfully created." || echo "Failed to create!" #!/bin/bash # model_weight_upload.sh: copy LOCAL_MODEL_DIR into REMOTE_MODEL_DIR on the PersistentVolume mounted in the loader Pod. # Requires: GDCS_WORKLOAD_NAMESPACE, GDCS_MODEL_LOADER_POD_NAME, LOCAL_MODEL_DIR, REMOTE_MODEL_DIR # Optional: REMOTE_MOUNT_DIR (PersistentVolume mount path, defaults to REMOTE_MODEL_DIR) - its lost+found directory is removed. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o errexit set -o nounset set -o pipefail # Ensure LOCAL_MODEL_DIR is set if [[ -z "${LOCAL_MODEL_DIR:-}" ]]; then echo "LOCAL_MODEL_DIR must be set, exiting..." exit 1 fi # Ensure REMOTE_MODEL_DIR is set if [[ -z "${REMOTE_MODEL_DIR:-}" ]]; then echo "REMOTE_MODEL_DIR must be set, exiting..." exit 1 fi REMOTE_MOUNT_DIR="${REMOTE_MOUNT_DIR:-${REMOTE_MODEL_DIR}}" KEEPALIVE_INTERVAL_SEC=300 start_session_keepalive() { echo "[KeepAlive] Started background heartbeat (every ${KEEPALIVE_INTERVAL_SEC}s)." while true; do sleep "${KEEPALIVE_INTERVAL_SEC}" # Lightweight API request to prevent session inactivity logout if kubectl get namespace "${GDCS_WORKLOAD_NAMESPACE}" >/dev/null 2>&1; then echo "[KeepAlive] Session heartbeat sent at $(date)" else echo "[KeepAlive] Heartbeat failed or context expired at $(date)" fi done } start_session_keepalive & MY_KEEPALIVE_PID=$! cleanup() { echo "[Cleanup] Stopping keep-alive process (PID: ${MY_KEEPALIVE_PID})..." kill "${MY_KEEPALIVE_PID}" 2>/dev/null || true wait "${MY_KEEPALIVE_PID}" 2>/dev/null || true echo "[Cleanup] Session keep-alive terminated." } trap cleanup EXIT INT TERM # Remove the lost+found directory kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- rm -rf "${REMOTE_MOUNT_DIR}/lost+found" # Create the model directory kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- mkdir -p "${REMOTE_MODEL_DIR}" # Loop through all files in the local directory FAILED_FILES=() MAX_UPLOAD_ATTEMPTS="${MAX_UPLOAD_ATTEMPTS:-3}" remote_size_of() { # Prints the remote file size (0 if the file does not exist) kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" --namespace="${GDCS_WORKLOAD_NAMESPACE}" -- stat -c%s "$1" 2>/dev/null || echo 0 } # Loop through all files in the local directory (process substitution keeps FAILED_FILES in this shell) while read -r local_file; do # Get relative path rel_path="${local_file#"${LOCAL_MODEL_DIR}"/}" remote_file="${REMOTE_MODEL_DIR}/${rel_path}" # Get local file size local_size=$(stat -L -c%s "${local_file}") # Get remote file size (returns 0 if file does not exist) remote_size=$(remote_size_of "${remote_file}") if [ "${local_size}" -eq "${remote_size}" ]; then echo "[Loader] Skipping: ${rel_path} (Size matches)" continue fi if [ "${remote_size}" -ne "0" ]; then echo "[Loader] Mismatch: ${rel_path} (${remote_size} remote vs ${local_size} local)" fi # Ensure remote parent subdirectories exist if your model has folders remote_parent=$(dirname "${remote_file}") kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- mkdir -p "${remote_parent}" attempt=1 while [ "${attempt}" -le "${MAX_UPLOAD_ATTEMPTS}" ]; do echo "[Loader] Uploading: ${rel_path} (${local_size} bytes, attempt ${attempt}/${MAX_UPLOAD_ATTEMPTS})..." # Copy the individual file using GNU tar streaming. # Note: 'kubectl cp' uses Go's archive/tar, which defaults to PAX format for files > 8 GiB. # BusyBox's tar in the loader pod does not parse PAX size headers and fails with 'tar: invalid tar magic'. # Streaming via GNU tar (--format=gnu) uses base-256 binary encoding for files > 8 GiB, which BusyBox tar supports. tar --format=gnu -C "$(dirname "${local_file}")" -cf - "$(basename "${local_file}")" | \ kubectl exec -i "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- tar -xf - -C "${remote_parent}" || true # Verify: an interrupted exec stream leaves a truncated file behind without an error remote_size=$(remote_size_of "${remote_file}") if [ "${local_size}" -eq "${remote_size}" ]; then echo "[Loader] Verified: ${rel_path} (${remote_size} bytes)" break fi echo "[Loader] Incomplete upload: ${rel_path} (${remote_size} remote vs ${local_size} local), retrying..." attempt=$((attempt + 1)) done if [ "${local_size}" -ne "${remote_size}" ]; then FAILED_FILES+=("${rel_path}") fi done < <(find -L "${LOCAL_MODEL_DIR}" -type f) if [ "${#FAILED_FILES[@]}" -ne 0 ]; then echo "[Loader] FAILED: ${#FAILED_FILES[@]} file(s) could not be uploaded completely after ${MAX_UPLOAD_ATTEMPTS} attempts:" printf '[Loader] %s\n' "${FAILED_FILES[@]}" exit 1 fi echo "[Loader] Completed" # Fix file permissions kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- chown 0:0 -R "${REMOTE_MODEL_DIR}" # Flush the volume before the loader Pod is deleted (a detached volume with unwritten page cache loses data) kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- sync EOF chmod u+x "${GDCS_IMPLEMENTATION_HOME}/model_weight_upload.sh"
モデルのデプロイ
- vLLM を使用した Gemma モデルのユーザーガイド: Gemma 4 E2B、E4B、12B、26B-A4B、31B。GDC エアギャップの GPU オプションを使用します。
- vLLM を使用した Mistral モデルのユーザーガイド: Mistral 7B Instruct、Mistral NeMo 12B、Mistral Small 3.2 24B、Ministral 8B、Mixtral 8x7B、Magistral Small 1.2(GDC エアギャップの GPU オプションを使用)