17 min de lectura

Objetivos de aprendizaje

Al final de este módulo, podrás:

  • Aprovisionar un IONOS CLOUD Container Registry y sus tokens de acceso con Terraform utilizando los recursos `ionoscloud_container_registry` y `ionoscloud_container_registry_token`
  • Autenticar la CLI de Docker en un registro mediante un `docker login` con alcance de token e impulsar imágenes de producción de varias etapas
  • Crear un pipeline de imágenes reproducible que etiquete las imágenes con el SHA de Git y las impulse al punto de acceso del registro
  • Integrar las credenciales del registro en GitHub Actions y GitLab CI como secretos y ejecutar `docker login` dentro de un pipeline
  • Configurar la recolección de basura y el análisis de vulnerabilidades, y trabajar dentro del modelo de tokens por registro del sistema

Unidad 3.1: Container Registry y pipelines de imágenes

Introducción

Está a punto de contenerizar el servicio de API y el frontend web de TaskBoard y necesita un registro al que enviar las imágenes antes de que Managed Kubernetes pueda descargarlas. IONOS CLOUD Container Registry le proporciona uno o más registros dedicados y con autenticación que implementan la API HTTP V2 de Docker Registry y aceptan cualquier artefacto compatible con OCI. Todo lo que realice con él, aprovisionamiento, creación de tokens, inicio de sesión, envío y recopilación de basura, se gestiona a través de Terraform, la API REST y la CLI de Docker.

Esta unidad comienza a nivel de código. Aprovisionará un registro como Infraestructura como Código, emitirá un token con ámbito limitado, autenticará Docker y construirá la pipeline de build-tag-push en la que depende el resto del Módulo 3. Dos restricciones específicas de IONOS CLOUD determinan cada ejemplo aquí: la autenticación es exclusivamente basada en tokens, sin control de acceso basado en roles y sin ACL por repositorio, y el nombre de host del registro no existe hasta que el registro alcanza el estado Running. Ambas le causarán problemas en CI si las ignora, por lo que están integradas en los patrones de código que se muestran a continuación.

1. Aprovisionamiento del registro con Terraform

El registro es un recurso de primera clase de Terraform. Usted declara un nombre y una ubicación, aplica los cambios y espera a que el aprovisionamiento asíncrono se complete antes de que se asigne el nombre de host. Los atributos name y location son inmutables tras la creación, por lo que la elección de estos valores es una decisión irreversible para cada registro.

El nombre del registro debe tener como máximo 63 caracteres. Si intenta crear un registro con un nombre que ya está en uso en toda la plataforma, la API devuelve HTTP 409 Conflict con el mensaje already exists: name is not available. Considere el nombre como un identificador global, no como uno por cuenta, y prefíjelo con su proyecto para evitar colisiones.

1.1 El recurso ionoscloud_container_registry

La ubicación debe ser una ubicación admitida de Container Registry. Container Registry está disponible en de/fra (DE/FRA), por lo que el valor de location es de/fra.

terraform {
  required_providers {
    ionoscloud = {
      source  = "ionos-cloud/ionoscloud"
      version = ">= 6.0.0"
    }
  }
}

provider "ionoscloud" {
  # token read from IONOS_TOKEN env var
}

resource "ionoscloud_container_registry" "taskboard" {
  name     = "taskboard-prod"
  location = "de/fra"

  garbage_collection_schedule {
    days = ["Saturday", "Sunday"]
    time = "19:30:00+00:00"
  }
}

output "registry_hostname" {
  value = ionoscloud_container_registry.taskboard.hostname
}

La salida de hostname sigue el patrón {registry-name}.cr.{location-with-dash}.ionos.com. Permanece vacía hasta que el registro alcanza Running, por lo que cualquier paso aguas abajo que la lea debe ejecutarse después de que apply se complete por completo. El registro tiene solo dos estados de ciclo de vida, New y Running, y solo Running es utilizable.

1.2 Aprovisionamiento mediante la API REST

Si aprovisiona fuera de Terraform, el registro se crea mediante una POST al punto de acceso de registros. Se aplican las mismas propiedades garbageCollectionSchedule, location y name.

curl --location \
  --request POST 'https://api.ionos.com/containerregistries/registries' \
  --header "Authorization: Bearer ${IONOS_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "properties": {
      "garbageCollectionSchedule": {
        "days": ["Saturday", "Sunday"],
        "time": "19:30:00+00:00"
      },
      "location": "de/fra",
      "name": "taskboard-prod"
    }
  }'

La respuesta 201 no incluye un nombre de host porque el host aún no está asignado. Realice consultas periódicas a GET /containerregistries/registries/{registryId} hasta que el estado sea Running antes de leer el nombre de host. Las llamadas de lista utilizan paginación por token: pase limit como parámetro de consulta y siga nextPageToken para recorrer grandes colecciones.

2. Tokens and Authentication

La autenticación se basa exclusivamente en tokens docker login. No hay RBAC, no hay repositorios por equipo y no hay acceso anónimo o a repositorios públicos. Un token es la unidad de acceso, y se delimita por registro o por ruta de repositorio mediante un conjunto de acciones permitidas.

2.1 Creación de un token con Terraform

Un token tiene un nombre, un estado, una fecha de expiración opcional y uno o más ámbitos. Cada ámbito tiene un tipo de Registry o Repository, una ruta y una lista de acciones tomada de pull, push y delete. El nombre del token debe tener de 3 a 63 caracteres, comenzar con a-z, terminar con un carácter alfanumérico y contener solo caracteres alfanuméricos y guiones, y es inmutable después de su creación.

resource "ionoscloud_container_registry_token" "ci_push" {
  container_registry_id = ionoscloud_container_registry.taskboard.id
  name                  = "ci-push"
  status                = "enabled"

  # minimum expiry is 1 hour in the future; on expiry the token is DELETED
  expiry_date = "2027-01-01T00:00:00Z"

  scopes {
    type    = "Repository"
    name    = "taskboard/*"
    actions = ["push", "pull"]
  }
}

output "ci_push_password" {
  value     = ionoscloud_container_registry_token.ci_push.password
  sensitive = true
}

Dos comportamientos son importantes para la automatización. La contraseña del token se muestra una sola vez, por lo que debe capturarse de inmediato desde el estado de Terraform o la respuesta de la API y almacenarse en su gestor de secretos. Cuando se alcanza la fecha de vencimiento, el token se elimina, no se desactiva, por lo que una tarea de rotación debe crear el token de reemplazo antes de que el anterior venza, o su pipeline perderá el acceso en medio de la implementación.

2.2 Creación de un token mediante la API e inicio de sesión

El punto final del token es POST /containerregistries/registries/{registryId}/tokens. Los tokens existen en dos tipos: un token de acceso permanente al registro y uno temporal limitado por expiryDate. El vencimiento mínimo es de una hora en el futuro.

curl --location \
  --request POST "https://api.ionos.com/containerregistries/registries/${REGISTRY_ID}/tokens" \
  --header "Authorization: Bearer ${IONOS_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "properties": {
      "name": "ci-push",
      "status": "enabled",
      "scopes": [
        { "type": "Repository", "name": "taskboard/*", "actions": ["push", "pull"] }
      ]
    }
  }'

La respuesta incluye las credenciales una sola vez. Utilice el nombre del token como nombre de usuario y la contraseña del token como contraseña para docker login contra el nombre de host del registro.

echo "${CR_TOKEN_PASSWORD}" | docker login taskboard-prod.cr.de-fra.ionos.com \
  --username ci-push \
  --password-stdin

Un token se encuentra en uno de tres estados: enabled, disabled o deleted. Desactive un token para revocar el acceso de inmediato sin eliminarlo; elimínelo cuando ya no sea necesario.

2.3 Elección de cómo emitir y usar credenciales

La siguiente tabla compara las rutas de acceso para las credenciales del registro, para que pueda elegir la adecuada según cada flujo de trabajo.

Enfoque Ideal para Herramientas Vida útil de la credencial
Recurso de token de Terraform Identidades de servicio de CI provisionadas como código HCL Hasta expiry_date (se elimina al expirar)
Creación de token de API Tareas de rotación mediante scripts curl/HTTP Hasta expiry_date o eliminación manual
docker login Empuje/obtención local de desarrollo Docker CLI Sesión, respaldada por token

Dado que los alcances son por registro o por ruta de repositorio y no existe RBAC, modele un token por pipeline de CI y uno por desarrollador, en lugar de compartir un único token amplio. Asigne a los tokens de CI el alcance push más pull en las rutas de repositorio que administran, y limite los tokens de despliegue de solo lectura al alcance pull únicamente.

3. Construcción y publicación de imágenes

Con un registro en ejecución y un token en mano, el flujo de trabajo de las imágenes es el estándar de Docker contra el nombre de host del registro. La disciplina que lo hace apto para producción son las construcciones en varias etapas para obtener imágenes pequeñas y las etiquetas inmutables de Git-SHA para implementaciones trazables.

3.1 Dockerfile en varias etapas para la API de TaskBoard

Una construcción en varias etapas compila o instala dependencias en una etapa de compilación voluminosa y copia solo los artefactos de tiempo de ejecución en una imagen final delgada. Las imágenes más pequeñas se descargan más rápido en Kubernetes y contienen menos paquetes para que el escáner de vulnerabilidades los señale.

FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "taskboard.api:app"]

3.2 Etiquetar con el SHA de Git y publicar

Etiquete cada compilación con el SHA inmutable de Git para que una imagen desplegada se pueda rastrear hasta un commit exacto. Una etiqueta latest es adecuada como referencia de conveniencia, pero nunca debe desplegarse desde ella; debe desplegarse desde el SHA.

REGISTRY=taskboard-prod.cr.de-fra.ionos.com
GIT_SHA=$(git rev-parse --short HEAD)

docker build -t "${REGISTRY}/taskboard/api:${GIT_SHA}" \
             -t "${REGISTRY}/taskboard/api:latest" .

docker push "${REGISTRY}/taskboard/api:${GIT_SHA}"
docker push "${REGISTRY}/taskboard/api:latest"

El patrón de pipeline consiste en construir, etiquetar con el SHA de Git, enviar a el registro y luego hacer referencia a la etiqueta SHA en el manifiesto de Kubernetes (cubierto en la Unidad 3.2). Tenga en cuenta que un token que otorga push también necesita pull: para enviar una imagen se requiere leer las capas existentes, por lo que un ámbito solo de envío es insuficiente y un token de push también debe incluir pull.

4. Recolección de basura y escaneo de vulnerabilidades

Dos funciones del registro se ejecutan en el lado del servidor y es recomendable configurarlas desde el inicio. La recolección de basura recupera almacenamiento de las capas sin referencias, y el escaneo de vulnerabilidades detecta CVE en los artefactos que se hayan subido.

4.1 Recolección de basura

La recolección de basura libera el almacenamiento ocupado por datos de capas que ya no son referenciados por ninguna etiqueta. Está deshabilitada de forma predeterminada porque el horario adecuado depende de la frecuencia de sus operaciones de carga y eliminación, por lo que debe configurarla de manera explícita. Ya la configuró en la recurso de Terraform de la sección 1.1 mediante el bloque garbage_collection_schedule con days y un time.

garbage_collection_schedule {
  days = ["Saturday", "Sunday"]
  time = "19:30:00+00:00"
}

Durante la recolección de basura, el registro entra en un estado de solo lectura: las operaciones de extracción continúan funcionando, pero las operaciones de carga se bloquean hasta que finalice la ejecución. Por lo tanto, prográbelo para una ventana de bajo tráfico a fin de evitar interrumpir las cargas y despliegues de CI/CD. Tenga en cuenta que la API de Docker Registry V2 no puede eliminar un repositorio completo; para eliminar un repositorio, debe llamar directamente a la API de IONOS CLOUD Container Registry con DELETE /containerregistries/registries/{id}/repositories/{url-encoded-name}, y el nombre del repositorio en la URL debe estar codificado como URL; por ejemplo, taskboard/api se convierte en taskboard%2Fapi, de lo contrario la llamada devuelve un error 404.

4.2 Escaneo de vulnerabilidades

El escaneo de vulnerabilidades analiza los artefactos cargados en busca de CVE conocidos. Es una función adicional, y una vez habilitada en un registro no puede desactivarse, por lo que debe habilitarla de manera deliberada. Los resultados del escaneo se informan por artefacto y por repositorio, e incluyen qué hallazgos tienen correcciones conocidas, lo que le permite condicionar la promoción de una imagen en función de sus resultados de escaneo en CI.

Trate el escaneo como una puerta de calidad: después de la etapa de carga, consulte los resultados del escaneo para el artefacto etiquetado con SHA y haga que la canalización falle si los hallazgos superan su umbral antes de que la etapa de despliegue continúe.

5. Integración con CI

En CI no se ejecuta un docker login interactivo. Se almacenan las credenciales del token como secretos de la canalización y se pasan a docker login --password-stdin. El nombre de host del registro, el nombre del token y la contraseña del token son los tres valores que su canalización necesita.

5.1 GitHub Actions

Almacene CR_HOSTNAME, CR_USERNAME y CR_PASSWORD como secretos del repositorio o del entorno, y luego inicie sesión y realice el envío en el flujo de trabajo.

name: build-and-push
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to IONOS CLOUD Container Registry
        run: |
          echo "${{ secrets.CR_PASSWORD }}" | docker login "${{ secrets.CR_HOSTNAME }}" \
            --username "${{ secrets.CR_USERNAME }}" --password-stdin

      - name: Build and push
        run: |
          SHA=$(git rev-parse --short HEAD)
          IMG="${{ secrets.CR_HOSTNAME }}/taskboard/api:${SHA}"
          docker build -t "$IMG" .
          docker push "$IMG"

5.2 GitLab CI

La pipeline equivalente de GitLab utiliza variables de CI/CD para los mismos tres valores y el servicio Docker-in-Docker para la compilación.

build-and-push:
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_HOST: tcp://docker:2376
  script:
    - echo "$CR_PASSWORD" | docker login "$CR_HOSTNAME" --username "$CR_USERNAME" --password-stdin
    - SHA=$(echo "$CI_COMMIT_SHA" | cut -c1-7)
    - docker build -t "$CR_HOSTNAME/taskboard/api:$SHA" .
    - docker push "$CR_HOSTNAME/taskboard/api:$SHA"
  only:
    - main

Dado que los tokens son el único modelo de credenciales, debe rotar el token de CI según un calendario y actualizar el secreto de forma sincronizada. Cada cliente tiene un límite de velocidad de 60 solicitudes por segundo y 100 conexiones simultáneas, lo cual es generoso para una sola canalización, pero conviene tenerlo en cuenta si se distribuyen compilaciones de matriz paralelas que todas envían datos al mismo tiempo.

Tarjeta rápida de referencia de la API

Puntos finales de API clave para Container Registry:

Método Punto final Descripción
GET /containerregistries/registries Listar registros (paginado mediante limit + nextPageToken)
POST /containerregistries/registries Crear un registro
GET /containerregistries/registries/{registryId} Obtener detalles del registro (y estado/nombre de host)
POST /containerregistries/registries/{registryId}/tokens Crear un token de acceso
DELETE /containerregistries/registries/{id}/repositories/{url-encoded-name} Eliminar un repositorio completo (la API V2 no puede)

URL base: https://api.ionos.com Autenticación: Authorization: Bearer <token>

Laboratorio de código

Objetivo: Aprovisionar un Container Registry, generar un token con ámbito limitado, construir la imagen de la API TaskBoard y publicarla con una etiqueta Git-SHA.

Requisitos previos:

  • Cuenta de IONOS CLOUD con un token de API exportado como IONOS_TOKEN
  • Terraform y Docker instalados localmente
  • Un Dockerfile para la API TaskBoard (consulte la sección 3.1)

Paso 1: Aprovisionar el registro y el token

export IONOS_TOKEN="your-api-token"
terraform init
terraform apply -auto-approve

Salida esperada:

ionoscloud_container_registry.taskboard: Creation complete
Outputs:
registry_hostname = "taskboard-prod.cr.de-fra.ionos.com"

Paso 2: Leer el nombre de host del registro y la contraseña del token

REGISTRY=$(terraform output -raw registry_hostname)
CR_PASSWORD=$(terraform output -raw ci_push_password)
echo "$REGISTRY"

Salida esperada:

taskboard-prod.cr.de-fra.ionos.com

Paso 3: Iniciar sesión en el registro

echo "$CR_PASSWORD" | docker login "$REGISTRY" --username ci-push --password-stdin

Salida esperada:

Login Succeeded

Paso 4: Crear la imagen

docker build -t "${REGISTRY}/taskboard/api:$(git rev-parse --short HEAD)" .

Salida esperada:

=> exporting to image
=> => naming to taskboard-prod.cr.de-fra.ionos.com/taskboard/api:a1b2c3d

Paso 5: Subir la imagen

docker push "${REGISTRY}/taskboard/api:$(git rev-parse --short HEAD)"

Salida esperada:

a1b2c3d: digest: sha256:... size: 1786

Paso 6: Verificar mediante la API

REGISTRY_ID=$(terraform output -raw registry_id)
curl -s --request GET \
  "https://api.ionos.com/containerregistries/registries/${REGISTRY_ID}/repositories" \
  --header "Authorization: Bearer ${IONOS_TOKEN}" | jq '.items[].id'

Salida esperada:

"taskboard/api"

Lista de verificación:

  • [ ] El registro alcanzó Running y expuso un nombre de host
  • [ ] docker login devolvió Login Succeeded
  • [ ] La imagen se envió con una etiqueta Git-SHA y es visible a través de la API de repositorios

Limpieza:

terraform destroy -auto-approve

Errores comunes

Errores de desarrollo a evitar con Container Registry:

  1. Lectura del nombre de host antes de que el registro esté en ejecución

    • Problema: El docker login de su pipeline falla con un error de resolución de DNS justo después de terraform apply.
    • Por qué ocurre: El nombre de host permanece vacío hasta que el registro sale del estado New y alcanza Running. Leerlo demasiado pronto devuelve un valor vacío o que no se puede resolver.
    • Solución: Condicione el paso de inicio de sesión al estado Running. Realice consultas al registro hasta que esté listo antes de iniciar sesión:
    until [ "$(curl -s -H "Authorization: Bearer $IONOS_TOKEN" \
      https://api.ionos.com/containerregistries/registries/$REGISTRY_ID \
      | jq -r '.metadata.state')" = "Running" ]; do sleep 5; done
    
  2. Token de push sin permiso de pull

    • Problema: docker push falla con un error de autorización, aunque el token tenga la acción push.
    • Por qué ocurre: El push lee las capas existentes para deduplicar, por lo que el push requiere pull. Un ámbito solo de push se rechaza.
    • Solución: Incluya siempre pull junto con push en el ámbito:
    actions = ["push", "pull"]
    
  3. Dejar que un token caduque durante una implementación

    • Problema: Una implementación de CI que funcionaba ayer ahora falla en la autenticación y el token simplemente ha desaparecido.
    • Por qué ocurre: Cuando se alcanza la fecha de caducidad de un token, este se elimina, no se desactiva, por lo que no existe un período de tolerancia ni una vía para reactivarlo.
    • Solución: Realice la rotación antes de la caducidad. Cree el token de reemplazo, actualice el secreto de CI y luego deje que el token anterior caduque. Nunca establezca una caducidad más corta que su cadencia de liberación, y recuerde que la caducidad mínima es de una hora en el futuro.

<translation>

Resumen

Ahora puede aprovisionar un IONOS CLOUD Container Registry como Infraestructura como Código, emitir tokens de acceso con ámbito limitado, autenticar la CLI de Docker y las pipelines de CI contra él, y enviar imágenes multi-etapa de nivel de producción etiquetadas por SHA de Git. También sabe cómo configurar la recolección de basura para recuperar almacenamiento y habilitar el escaneo de vulnerabilidades como puerta de promoción. Este registro es la fuente de verdad para las imágenes que la Unidad 3.2 desplegará en Managed Kubernetes.

El modelo de acceso solo con tokens del registro, sin RBAC y sin ACL por repositorio, determina cómo organiza las credenciales: un token con ámbito limitado por pipeline y por desarrollador, rotado antes de su vencimiento, ya que los tokens vencidos se eliminan por completo. Tenga en cuenta la restricción de nombre de host después de Running para cada flujo de trabajo automatizado, y la pipeline de compilación, etiquetado y envío que construyó aquí se convierte en la primera etapa del flujo completo de CI/CD en la Unidad 3.3.

Puntos clave:

  • Aprovisione registros con ionoscloud_container_registry; name y location son inmutables y el nombre es único a nivel global (un duplicado devuelve HTTP 409)
  • El nombre de host sigue {name}.cr.{location-with-dash}.ionos.com y está vacío hasta que el registro esté Running
  • La autenticación es basada en tokens docker login únicamente, con ámbitos de Registry o Repository y acciones pull, push, delete; push requiere pull
  • Las contraseñas de los tokens se muestran una sola vez y los tokens vencidos se eliminan, no se desactivan, por lo que captúrelos y rótelos de forma proactiva
  • La recolección de basura está deshabilitada de forma predeterminada y se configura en un horario; el escaneo de vulnerabilidades es un complemento que no puede deshabilitarse una vez habilitado

Terminología importante:

  • Token de acceso del registro: La credencial para docker login, con ámbito por registro o por ruta de repositorio; viene en tipos permanentes y temporales (con límite de vencimiento)
  • Ámbito: La concesión de permisos de un token, un tipo (Registry o Repository), una ruta y un conjunto de acciones de pull, push, delete
  • Recolección de basura: Recuperación en el servidor de capas de imagen no referenciadas, deshabilitada de forma predeterminada y ejecutada en un horario de día y hora configurado
  • Escaneo de vulnerabilidades: Un complemento que analiza los artefactos enviados en busca de CVE por artefacto y por repositorio; irreversible una vez habilitado
  • Etiqueta Git-SHA: Una etiqueta de imagen inmutable establecida en el SHA del commit, de modo que una imagen desplegada se corresponda con un commit exacto

Próximos pasos

Continuar aprendiendo: Unidad 3.2: Despliegue y operaciones de Kubernetes

Temas relacionados: