Este documento fornece instruções detalhadas para implantar modelos de pesos abertos, como Gemma, Llama e DeepSeek, em ambientes com isolamento físico do Google Distributed Cloud (GDC). Ele aborda o uso do Ollama para facilitar o uso, usando os recursos da plataforma GDC, incluindo Kubernetes, Harbor e recursos de acelerador.
Arquitetura
A solução envolve a implantação do Ollama como um back-end de exibição de inferência em contêiner (Deployment) em um cluster padrão. Os pesos do modelo são armazenados em
volumes permanentes que são dimensionados dinamicamente com base no diretório do modelo
baixado com 25% de sobrecarga, configurados com a
política de QoS de armazenamento system-performance-<org>-admin (30 IOPS por GiB com um
piso mínimo de 6.148 IOPS ou performance-rwo em que o armazenamento em blocos de alto desempenho
está ativado) e preenchidos usando um fluxo de trabalho de carregador de modelos. Os Services do Kubernetes do tipo LoadBalancer expõem a API de back-end, e os objetos NetworkPolicy / ProjectNetworkPolicy protegem o acesso a ela.

Antes de começar
Antes de prosseguir com a implantação, verifique se o ambiente atende a todos os pré-requisitos necessários e se os utilitários de linha de comando necessários estão configurados corretamente. Configurar essas ferramentas na estação de trabalho é essencial para gerenciar Container Registry, interagir com clusters e automatizar o processo de implantação.
- Cluster padrão criado com recursos suficientes (CPU, memória, acelerador). Os requisitos mínimos de acelerador variam de acordo com o modelo.
- Instância do Harbor disponível e acessível.
- O IAM necessário foi aplicado.
- Estação de trabalho com a conectividade necessária com o ambiente e a Internet
Configuração do ambiente
Identity and Access Management
Verifique se as contas, os papéis e as permissões necessários do IAM estão configurados corretamente.
Papéis de usuário do GDC no projeto (RoleBinding no
namespace do projeto, concedido por um administrador do IAM do projeto):
- Leitor de instâncias do Harbor (
harbor-instance-viewer) - Criador de projetos do Harbor (
harbor-project-creator, somente se o projeto do Harbor ainda não existir) - Administrador de cluster padrão (
standard-cluster-admin, obrigatório paragdcloud clusters get-credentials)
Papel de usuário do GDC no cluster padrão: os papéis do projeto anteriores não concedem permissões dentro do cluster. Um administrador do IAM do projeto também precisa vincular o usuário ao StandardClusterRole cluster-admin com um StandardClusterRoleBinding no namespace do projeto no servidor de API Management. A vinculação é propagada para os clusters padrão do projeto em segundos (status.clusters[].conditions mostra Propagated=True).
cat <<EOF | kubectl --kubeconfig MANAGEMENT_API_SERVER apply -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
Substitua:
MANAGEMENT_API_SERVER: o caminho para o arquivo kubeconfig do servidor de API de gerenciamento.BINDING_NAME: um nome para a vinculação, por exemplo,alex-cluster-admin(letras minúsculas, dígitos e hifens).PROJECT: o nome do projeto.USER: o nome de usuário a ser vinculado, exatamente como o provedor de identidade o informa (por exemplo, um endereço de e-mail).
Assistência do operador de infraestrutura (somente quando performance-rwo não está
disponível no cluster padrão): os guias do usuário criam o modelo
PersistentVolumeClaim com o StorageClass standard-rwo padrão, que
limita os volumes do modelo de tamanho correto a 3 IOPS por GiB. Peça ao operador de infraestrutura
para aplicar a política de QoS system-performance-<ORG>-admin ao volume
que apoia o modelo PersistentVolumeClaim para que ele receba o piso de 6.148 IOPS. Os guias do usuário mostram os valores que o operador de infraestrutura precisa.
Permissões da conta de robô do Harbor para crane:
- Listar repositório
- Repositório de pull
- Repositório de push
- Ler artefato
- Listar artefato
- Criar tag
- Tag de lista
Permissões da conta de robô do Harbor para extrações de imagens do Kubernetes:
- Listar repositório
- Repositório de pull
- Ler artefato
- Listar artefato
- Tag de lista
Estação de trabalho
Execute todos os comandos deste guia em uma estação de trabalho com as ferramentas e a conectividade necessárias para o ambiente e a Internet, a menos que uma etapa indique o contrário.
Requisitos
As seguintes ferramentas precisam ser instaladas na estação de trabalho:
crane: gerenciar e copiar imagens de contêiner entre registros (documentação).gdcloud: interface de linha de comando (CLI) para gerenciar recursos do GDC (documentação).kubectl: interface de linha de comando (CLI) usada para se comunicar com um cluster do Kubernetes e gerenciá-lo.jq: processador JSON de linha de comando leve e flexível.ollama: ferramenta de código aberto que permite baixar e executar modelos.yq: processador YAML de linha de comando portátil.
Configuração da estação de trabalho
As seguintes informações sobre o ambiente são necessárias para a configuração da estação de trabalho:
GDC_STANDARD_CLUSTER_NAME: o nome do cluster padrão do GDC.GDC_DOMAIN_SUFFIX: o sufixo de domínio do ambiente do GDC (por exemplo,gdc.example.com).GDC_ORG: o nome da organização do GDC.GDC_PROJECT: o nome do projeto do GDC.GDC_ZONE: o nome da zona de implantação do GDC.GDC_HARBOR_INSTANCE_NAME: o nome da instância do Harbor no projeto.GDCS_HARBOR_PROJECT_NAME: o nome do projeto do Harbor a ser usado para imagens (padrão:solutions).GDCS_HARBOR_CRANE_ROBOT_NAME: o nome da conta de robô do Harborcrane.GDCS_HARBOR_CRANE_ROBOT_TOKEN: o token de autenticação da conta de robô do Harborcrane.GDCS_HARBOR_K8S_ROBOT_NAME: o nome da conta de robô de extração de imagens do Kubernetes do Harbor.GDCS_HARBOR_K8S_ROBOT_TOKEN: o token de autenticação da conta do robô de extração de imagens do Kubernetes do Harbor.
Depois de coletar os valores de todas as variáveis necessárias, gere o arquivo de variáveis de ambiente. Depois de criado, é possível editar manualmente o arquivo a qualquer momento.
Crie os diretórios raiz da solução e a pasta de 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/*Crie o arquivo de configuração do ambiente da plataforma:
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}" EOFSubstitua:
STANDARD_CLUSTER_NAME: nome do cluster padrão do GDC.DOMAIN_SUFFIX: sufixo de domínio do GDC.ORG: organização do GDC.PROJECT: projeto do GDC.ZONE: zona do GDC.HARBOR_INSTANCE_NAME: nome da instância do GDC Harbor.
Crie o arquivo de configuração do ambiente de registro:
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}" EOFSubstitua:
HARBOR_CRANE_ROBOT_NAME: o nome completo da conta de robô do Harbor paracrane, conforme mostrado pelo Harbor, incluindo o prefixorobot$. Mantenha as aspas simples para que o shell não expanda$.HARBOR_K8S_ROBOT_NAME: o nome completo da conta de robô do Harbor para extrações de imagens do Kubernetes, conforme mostrado pelo Harbor, incluindo o prefixorobot$. Mantenha as aspas simples para que o shell não expanda$.
Adicione os tokens aos arquivos de secrets:
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 historySubstitua:
CRANE_ROBOT_TOKEN: a chave secreta da conta de robôcrane.KUBERNETES_ROBOT_TOKEN: o secret da conta de robô de extração de imagens do Kubernetes.
Crie o arquivo do carregador de ambiente raiz:
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
Configurar variáveis de solução
Crie o diretório de implementação da solução:
mkdir -p ${HOME}/gdcag-solutions/open-weight-models/ollama/env.dCrie o arquivo de configuração do acelerador:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/ollama/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" EOFSubstitua:
WORKLOAD_ACCELERATOR_TYPE: o nome do recurso de GPU que os nós de GPU expõem, por exemplo,nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE. Os guias do usuário listam o nome do recurso de cada GPU que o GDC com isolamento físico oferece. Depois de recuperar as credenciais do cluster, é possível listar os nomes no cluster comkubectl get nodes --output=json | jq -r '[.items[].status.allocatable | keys[] | select(startswith("nvidia.com/"))] | unique[]'.
Crie o arquivo de configuração do ambiente da solução:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/ollama/env.d/ollama.sh && echo "Successfully created." || echo "Failed to create!" # Images export GDCS_BUSYBOX_IMAGE_TAG="1.38.0" export GDCS_OLLAMA_IMAGE_TAG="0.32.14" # 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" EOFCrie o arquivo do carregador do ambiente de implementação:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/ollama/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/ollama" 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/ollama.sh" EOFEdite e revise os arquivos de ambiente com seu editor preferido:
${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/ollama/env.d/accelerator.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/ollama/env.d/ollama.shUse o comando source no arquivo de ambiente:
source ${HOME}/gdcag-solutions/open-weight-models/ollama/env.shO resultado será o seguinte:
GDCS_ROOT_HOME=HOME_DIRECTORY_PATH/gdcag-solutions GDCS_IMPLEMENTATION_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/open-weight-models/ollama
GDC
Este guia pressupõe que sua estação de trabalho esteja configurada para confiar nos certificados TLS do ambiente do GDC e da instância do Harbor.
Configurar
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}"Autentique-se no ambiente do GDC:
gdcloud auth login
Cluster
Recupere as credenciais do cluster:
gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \ --project="${GDC_PROJECT}" \ --standard \ --zone="${GDC_ZONE}"Verifique a conectividade com o cluster:
kubectl get nodes -L node.cluster.private.gdc.goog/machine-class
Preparação para a migração de artefatos
- Verifique a conectividade e a configuração do Artifact Registry.
Crie um arquivo de configuração do Docker para o crane. Uma conta de robô é usada para enviar camadas de imagem grandes para evitar tempos limite de token de autenticação ao usar o auxiliar de credenciais do Serviço Gerenciado do Harbor (MHS, na sigla em inglês) (docker-credential-mhs) com uma conta de usuário:
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}"Crie um arquivo de configuração do Docker para o Kubernetes:
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}"Crie o script
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"Defina a lista de imagens de contêiner necessárias para a solução:
declare -a GDCS_REGISTRY_IMAGES=( "docker.io/library/busybox:${GDCS_BUSYBOX_IMAGE_TAG}" "docker.io/ollama/ollama:${GDCS_OLLAMA_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)Envie as imagens de contêiner necessárias para o Artifact Registry:
${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
Preparação do modelo
Crie o script
ollama_model_weight_download.sh. Ele executa umollama servelocal temporário comOLLAMA_MODELSapontado para o diretório de download e extrai o modelo para ele:cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/ollama_model_weight_download.sh && echo "Successfully created." || echo "Failed to create!" #!/bin/bash # ollama_model_weight_download.sh: pull GDCS_OLLAMA_MODEL into GDCS_OLLAMA_MODEL_DOWNLOAD_DIR with a temporary local Ollama server. # Requires: GDCS_OLLAMA_MODEL, GDCS_OLLAMA_MODEL_DOWNLOAD_DIR SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o nounset set -o pipefail # Ensure GDCS_OLLAMA_MODEL_DOWNLOAD_DIR is set if [[ -z "${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR:-}" ]]; then echo "GDCS_OLLAMA_MODEL_DOWNLOAD_DIR must be set, exiting..." exit 1 fi mkdir -p "${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR}" # Use a dedicated local port so an already running Ollama instance is not reused export OLLAMA_HOST="${OLLAMA_DOWNLOAD_HOST:-127.0.0.1:11435}" export OLLAMA_MODELS="${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR}" ollama serve > "${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR}/ollama-serve.log" 2>&1 & OLLAMA_PID=$! # Set up a trap to kill ollama serve when the script exits trap "kill ${OLLAMA_PID} 2>/dev/null" EXIT INT TERM # Wait for the local server for i in $(seq 1 30); do ollama list > /dev/null 2>&1 && break sleep 1 done ollama pull "${GDCS_OLLAMA_MODEL}" || exit 1 rm -f "${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR}/ollama-serve.log" echo "Model '${GDCS_OLLAMA_MODEL}' is available in '${GDCS_OLLAMA_MODEL_DOWNLOAD_DIR}'." EOF chmod u+x "${GDCS_IMPLEMENTATION_HOME}/ollama_model_weight_download.sh"Crie o script
model_weight_upload.sh. Ele copia um diretório de modelo local para oPersistentVolumeusando o carregador de modelosPod(streaming GNUtar, incluindo arquivos maiores que 8 GiB) e ignora arquivos que já estão presentes com o mesmo tamanho: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"
Implantar um modelo
- Guia do usuário de modelos do Gemma com o Ollama: Gemma 4 E2B, E4B, 12B, 26B-A4B e 31B, com as opções de GPU do GDC com isolamento físico