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
Dockerfilepara 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ó
Runningy expuso un nombre de host - [ ]
docker logindevolvió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:
-
Lectura del nombre de host antes de que el registro esté en ejecución
- Problema: El
docker loginde su pipeline falla con un error de resolución de DNS justo después deterraform apply. - Por qué ocurre: El nombre de host permanece vacío hasta que el registro sale del estado
Newy alcanzaRunning. 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 - Problema: El
-
Token de push sin permiso de pull
- Problema:
docker pushfalla con un error de autorización, aunque el token tenga la acciónpush. - Por qué ocurre: El push lee las capas existentes para deduplicar, por lo que el push requiere pull. Un ámbito solo de
pushse rechaza. - Solución: Incluya siempre
pulljunto conpushen el ámbito:
actions = ["push", "pull"] - Problema:
-
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;nameylocationson 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.comy está vacío hasta que el registro estéRunning - La autenticación es basada en tokens
docker loginúnicamente, con ámbitos deRegistryoRepositoryy accionespull,push,delete;pushrequierepull - 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 (
RegistryoRepository), una ruta y un conjunto de acciones depull,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: