17 min de lectura

Objetivos de aprendizaje

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

  • Proporcionar un registro de contenedores IONOS 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 utilizando un `docker login` con ámbito de token y empujar imágenes de producción de varios escenarios
  • Construir una tubería de imágenes reproductible que etiqueta las imágenes con el SHA de Git y las empuja al punto de conexión del registro
  • Conectar las credenciales del registro en GitHub Actions y GitLab CI como secretos y ejecutar `docker login` dentro de una tubería
  • Configurar la recolección de basura y el análisis de vulnerabilidades, y trabajar dentro del modelo de token por registro

Unidad 3.1: Registro de contenedores y pipelines de imágenes

Introducción

Está a punto de contenerizar el servicio y el frontend web de TaskBoard's API y necesita un registro para empujar las imágenes antes de que Managed Kubernetes pueda extraerlas. El Registro de Contenedores de IONOS le da uno o más registros dedicados y autenticados que hablan el Registro Docker HTTP API V2 y aceptan cualquier artefacto compatible con OCI. Todo lo que hace con él, provisionamiento, creación de tokens, inicio de sesión, empuje y recolección de basura, se realiza a través de Terraform, el REST API, y la CLI de Docker.

Esta unidad comienza en el nivel de código. Usted provisionará un registro como Infraestructura como Código, acuñará un token con ámbito, autenticará Docker, y construirá la tubería de construcción-etiqueta-empuje que depende del REST de la Unidad 3. Dos restricciones específicas de IONOS dan forma a cada ejemplo aquí: la autenticación es solo 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 lo afectarán en CI si las ignora, así que están integradas en los patrones de código a continuación.

1. Provisionar el Registro con Terraform

El registro es un recurso de primera clase de Terraform. Debe declarar un nombre y una ubicación, aplicar y esperar a que se complete la provisión asíncrona antes de que se asigne el nombre de host. Los name y location son inmutables después de la creación, por lo que elegirlos es una decisión de un solo sentido por 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, el API devuelve HTTP 409 Conflict con el mensaje already exists: name is not available. Trate el nombre como un identificador global, no como uno por cuenta, y prefixéelo con su proyecto para evitar colisiones.

1.1 El recurso ionoscloud_container_registry

La ubicación debe ser una ubicación de Container Registry compatible. Container Registry está disponible en de/fra (DE/FRA), por lo que su valor 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 hostname sigue el patrón {registry-name}.cr.{location-with-dash}.ionos.com. Está vacía hasta que el registro alcanza Running, por lo que cualquier paso descendente que lo lea debe ejecutarse después de que apply se complete por completo. El registro solo tiene dos estados de ciclo de vida, New y Running, y solo Running es utilizable.

1.2 Provisionar mediante el REST API

Si se provisiona fuera de Terraform, el registro se crea con un POST al punto de conexión de los registros. Las mismas propiedades garbageCollectionSchedule, location y name se aplican.

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 se ha asignado. Realice una encuesta GET /containerregistries/registries/{registryId} hasta que el estado sea Running antes de leer el nombre de host. Las llamadas de lista están paginadas por tokens: pase limit como parámetro de consulta y siga nextPageToken para pasar por grandes colecciones.

2. Tokens y Autenticación

La autenticación es basada en tokens docker login solamente. 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 usted puede limitarlo por registro o por ruta de repositorio con un conjunto de acciones permitidas.

2.1 Crear un token con Terraform

Un token tiene un nombre, un estado, una fecha de caducidad opcional y uno o más ámbitos. Cada ámbito tiene un tipo de Registry o Repository, una ruta y una lista de acciones que se pueden realizar con pull, push, y delete. El nombre del token debe tener entre 3 y 63 caracteres, comenzar con una letra minúscula, 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, así que es importante capturarla desde el estado de Terraform o la respuesta de API de inmediato y almacenarla en su gestor de secretos. Cuando se alcanza la fecha de caducidad, el token se elimina, no se deshabilita, por lo que un trabajo de rotación debe crear el reemplazo antes de que el antiguo caduque o su canal de implementación pierda acceso a mitad de la implementación.

2.2 Crear un token a través de API y iniciar sesión

El punto final del token es POST /containerregistries/registries/{registryId}/tokens. Los tokens vienen en dos tipos, un token de acceso permanente al registro y uno temporal limitado por expiryDate. La fecha de caducidad mínima 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 lleva las credenciales una sola vez. Utilice el nombre del token como el nombre de usuario y la contraseña del token como la 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 vive en uno de tres estados, enabled, disabled, o deleted. Deshabilite un token para revocar el acceso de inmediato sin eliminarlo; elimínelo cuando ya no sea necesario.

2.3 Elegir cómo acuñar y utilizar credenciales

La siguiente tabla compara las rutas de acceso para las credenciales del registro para que pueda elegir la correcta para cada flujo de trabajo.

Enfoque Mejor para Herramientas Tiempo de vida de la credencial
Recurso de token de Terraform Identidades de servicio de CI provisionadas como código HCL Hasta expiry_date (eliminado en la fecha de caducidad)
Crear token de API Trabajos de rotación scripteados curl/HTTP Hasta expiry_date o eliminación manual
docker login Desarrollo local de push/pull CLI de Docker Sesión, respaldada por token

Dado que los ámbitos son por registro o por ruta de repositorio y no hay RBAC, modele un token por cada canal de CI y uno por desarrollador en lugar de compartir un solo token amplio. Limite los tokens de CI a push más pull en las rutas de repositorio que poseen, y limite los tokens de implementación de solo lectura a pull solamente.

3. Creación y envío de imágenes

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

3.1 Multi-etapa Dockerfile para la TaskBoard API

Una compilación en varias etapas compila o instala dependencias en una etapa de constructora gruesa 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 llevan menos paquetes para que el escáner de vulnerabilidades los marque.

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 enviar

Etiquete cada compilación con el SHA de Git inmutable para que una imagen implementada se mapee de regreso a un compromiso exacto. Una etiqueta latest es adecuada como un puntero de conveniencia, pero nunca implemente desde ella; implemente 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 tubería es compilar, etiquetar con el SHA de Git, enviar al 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 otorgue push también necesita pull: enviar una imagen requiere leer capas existentes, por lo que un alcance de envío solo es insuficiente y un token push también debe llevar pull.

4. Recolección de basura y análisis de vulnerabilidades

Dos características del registro se ejecutan en el servidor y vale la pena configurarlas temprano. La recolección de basura recupera el almacenamiento de capas no referenciadas, y el análisis de vulnerabilidades muestra los CVE en los artefactos que ha enviado.

4.1 Recolección de basura

La recolección de basura libera el almacenamiento ocupado por los datos de capa que ya no están referenciados por ninguna etiqueta. Está deshabilitada de forma predeterminada porque el calendario adecuado depende de su cadencia de envío y eliminación, por lo que debe configurarla explícitamente. Ya la configuró en el recurso Terraform en la Sección 1.1 a través del 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 extracciones continúan funcionando, pero las inserciones están bloqueadas hasta que se complete la ejecución, así que prográmelo para una ventana de bajo tráfico para evitar interrumpir las inserciones y despliegues de CI/CD. Tenga en cuenta que el Registro V2 Docker API no puede eliminar un repositorio completo; para eliminar un repositorio, llame directamente al Registro de contenedores de IONOS API con DELETE /containerregistries/registries/{id}/repositories/{url-encoded-name}, y el nombre del repositorio en la URL debe estar codificado en URL, por ejemplo, taskboard/api se convierte en taskboard%2Fapi, o la llamada devuelve 404.

4.2 Análisis de vulnerabilidades

El análisis de vulnerabilidades analiza los artefactos insertados en busca de CVE conocidos. Es una función adicional, y una vez habilitada en un registro, no se puede deshabilitar, así que habilitela de forma deliberada. Los resultados del análisis se informan por artefacto y por repositorio, incluyendo qué hallazgos tienen soluciones conocidas, así que puede controlar la promoción de una imagen en función de los resultados del análisis en CI.

Trate el análisis como una puerta de calidad: después del paso de inserción, consulte los resultados del análisis para el artefacto con etiqueta SHA y haga que la canalización falle si los hallazgos superan su umbral antes de que proceda el paso de despliegue.

5. Integración de CI

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

5.1 Acciones de GitHub

Almacene CR_HOSTNAME, CR_USERNAME y CR_PASSWORD como secretos del repositorio o del entorno, luego inicie sesión y realice push 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 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 CI de GitLab

La canalización equivalente de GitLab utiliza variables CI/CD para los tres valores y el servicio Docker-en-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, rote el token de CI en un calendario y actualice el secreto al mismo tiempo. Cada cliente tiene un límite de velocidad de 60 solicitudes por segundo y 100 conexiones concurrentes, lo que es generoso para una sola canalización, pero vale la pena saber si usted ejecuta construcciones de matriz paralelas que empujan todo al mismo tiempo.

API Tarjeta de referencia rápida

Puntos de enlace clave de API para el Registro de contenedores:

Método Punto de enlace Descripción
GET /containerregistries/registries Lista de registros (paginada a través de 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 (V2 API no puede)

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

Laboratorio de código

Objetivo: Proporcionar un Registro de Contenedores, acuñar un token con ámbito, compilar la imagen de TaskBoard API y publicarla con una etiqueta Git-SHA.

Requisitos previos:

  • Cuenta de IONOS Cloud con token API exportado como IONOS_TOKEN
  • Terraform y Docker instalados localmente
  • Un Dockerfile para la TaskBoard API (ver Sección 3.1)

Paso 1: Proporcionar 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 hostname 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: Compilar 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: Publicar la imagen

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

Salida esperada:

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

Paso 6: Verificar a través de 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 hostname
  • [ ] docker login devolvió Login Succeeded
  • [ ] La imagen se publicó con una etiqueta Git-SHA y es visible a través de los repositorios API

Limpieza:

terraform destroy -auto-approve

Errores comunes

Errores de los desarrolladores que deben evitarse con Container Registry:

  1. Leyendo el nombre de host antes de que el registro esté en funcionamiento

    • Problema: La tubería de docker login falla con un error de resolución de DNS justo después de terraform apply.
    • Por qué sucede: El nombre de host está vacío hasta que el registro sale del estado New y alcanza Running. Leerlo demasiado pronto produce un valor vacío o no resoluble.
    • Solución: Limitar el paso de inicio de sesión al estado Running. Realizar una consulta 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. Enviar token sin permiso de extracción

    • Problema: docker push falla con un error de autorización aunque el token tenga la acción push.
    • Por qué sucede: Enviar lee capas existentes para deduplicar, por lo que enviar requiere extracción. Un alcance push-solo es rechazado.
    • Solución: Incluir siempre pull junto con push en el alcance:
    actions = ["push", "pull"]
    
  3. Dejar que un token expire en medio de la implementación

    • Problema: Una implementación de CI que funcionaba ayer ahora falla la autenticación, y el token simplemente ha desaparecido.
    • Por qué sucede: Cuando se alcanza la fecha de vencimiento de un token, se elimina, no se deshabilita, por lo que no hay un período de gracia y no hay camino de reinstalación.
    • Solución: Rotar antes del vencimiento. Crear el token de reemplazo, actualizar el secreto de CI, luego dejar que el antiguo expire. Nunca establecer un vencimiento más corto que su cadencia de lanzamiento, y recuerde que el vencimiento mínimo es de una hora en el futuro.

Resumen

Ahora puede aprovisionar un Registro de Contenedores de IONOS como Infraestructura como Código, acuñar tokens de acceso con ámbito, autenticar la CLI de Docker y las tuberías de CI contra ella, y empujar imágenes de varios escenarios con etiquetas producidas por Git SHA. También sabe cómo configurar la recolección de basura para recuperar almacenamiento y habilitar el análisis de vulnerabilidades como una puerta de promoción. Este registro es la fuente de verdad para las imágenes que la Unidad 3.2 implementará en Managed Kubernetes.

El modelo de acceso solo con tokens del registro, sin RBAC y sin ACLs por repositorio, determina cómo organiza las credenciales: un token con ámbito por tubería y por desarrollador, rotado antes de la expiración porque los tokens expirados se eliminan directamente. Mantenga en mente la restricción de hostname-después-de-ejecución para cada flujo de trabajo automatizado, y la tubería de construcción-etiqueta-empuje 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 (una copia devuelve HTTP 409)
  • El hostname 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 solo, con ámbitos de Registry o Repository y acciones pull, push, delete; push requiere pull
  • Las contraseñas de los tokens se muestran una vez y los tokens expirados se eliminan, no se deshabilitan, así que capture y rote proactivamente
  • La recolección de basura está deshabilitada por defecto y se configura en un horario; el análisis de vulnerabilidades es un complemento que no se puede deshabilitar 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 (limitados por expiración)
  • Ámbito: Una concesión de permiso de 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 lado del servidor de capas de imagen no referenciadas, deshabilitada por defecto y ejecutada en un horario de día/hora configurado
  • Análisis de vulnerabilidades: Un complemento que analiza los artefactos empujados para CVE por artefacto y por repositorio; irreversible una vez habilitado
  • Etiqueta Git-SHA: Una etiqueta de imagen inmutable establecida en el SHA del compromiso para que una imagen implementada se asocie con un compromiso exacto

Próximos pasos

Continuar aprendiendo: Unidad 3.2: Despliegue y operaciones de Kubernetes

Temas relacionados: