|

Cómo armar un pipeline CI/CD con Azure DevOps y AKS

En pocas palabras: Se arma conectando GitHub a Azure DevOps para build y test, empujando la imagen Docker a Azure Container Registry, y desplegando con Helm en AKS, mientras PostgreSQL Flexible Server 16 corre aparte, en red privada, dentro del resource group devops-aks-lab-rg-sa.

Desmond Goldsmith documentó en un artículo publicado el 13 de septiembre de 2026 en dev.to cómo armó un pipeline CI/CD Azure DevOps AKS completo, conectando Azure Container Registry, Helm y PostgreSQL Flexible Server por red privada. El resource group usado fue devops-aks-lab-rg-sa, en la región South Africa North, bajo una suscripción Azure for Students.

Un pipeline CI/CD Azure DevOps AKS es un flujo automatizado que toma código desde un repositorio (en este caso GitHub), lo compila y testea, construye una imagen Docker, la sube a un registro privado (Azure Container Registry) y despliega esa imagen en un clúster de Kubernetes administrado (AKS) usando Helm como gestor de charts. El objetivo de Goldsmith no era la app en sí, sino entender la infraestructura que la rodea.

En 30 segundos

  • El flujo va de GitHub a Azure DevOps, build+test, Docker, Azure Container Registry (dessydevopsacr), Helm y AKS.
  • PostgreSQL corre como Flexible Server versión 16 (SKU Standard_B1ms), no como pod dentro del clúster.
  • La subred dedicada a la base, postgres-subnet, usa el rango 10.0.2.0/24, separado del Pod CIDR 10.244.0.0/16 y el Service CIDR 10.240.0.0/16 de AKS.
  • La zona DNS privada private.postgres.database.azure.com resuelve el hostname del servidor a la IP privada 10.0.2.4.
  • La conexión se valida con un pod temporal llamado psql-test, imagen postgres:16, usando sslmode=require.

¿Por qué separar PostgreSQL del clúster de AKS en lugar de correrlo como pod?

Porque separar cómputo de datos evita que un problema en el clúster (un nodo caído, un rollout mal hecho) te tumbe la base. Goldsmith eligió Azure Database for PostgreSQL Flexible Server en vez de meter Postgres en un pod dentro de AKS, dejando que el clúster se ocupe solo de correr frontend y backend.

Ejemplo hipotético (no forma parte del proyecto documentado por Goldsmith, es solo para ilustrar el razonamiento): imaginemos un equipo que corre Postgres en un pod dentro de AKS y un nodo se reinicia por un update del sistema operativo subyacente. Si nadie configuró bien el storage persistente de ese pod, el resultado es pérdida de datos o, en el mejor de los casos, downtime de la base justo cuando el clúster está en medio de un rollout. Con Flexible Server ese escenario cambia de responsable: Azure administra el patching, los backups automáticos y la disponibilidad del motor, mientras el clúster se enfoca en las cargas de aplicación.

El tema es que esto no es gratis: se paga por un servicio administrado además de los nodos de AKS. Para un proyecto de aprendizaje con suscripción de estudiante, el SKU elegido fue Standard_B1ms, de las opciones más chicas disponibles. En un escenario real con tráfico moderado, ese tamaño de instancia probablemente se quede corto, y ahí es donde entra a jugar el trade-off entre costo y control que describimos más abajo.

Cómo se arma la registry de imágenes con Azure Container Registry sin cuenta admin

Azure Container Registry (ACR) es el repositorio intermedio donde el pipeline sube las imágenes Docker antes de que AKS las descargue para desplegarlas. Goldsmith creó el registro dessydevopsacr con SKU Basic y, algo que vale la pena remarcar, dejó la cuenta admin deshabilitada (admin-enabled false). Relacionado: usar un servidor MCP para gestionar pipelines.

¿Por qué molestarse en desactivar algo que viene habilitado por default? Porque la cuenta admin de ACR entrega un usuario y contraseña estáticos, del tipo credencial que queda dando vueltas en algún pipeline YAML o variable de entorno olvidada. En vez de eso, Azure DevOps se conecta mediante una service connection, un modelo de autenticación más acotado y auditable.

El proyecto terminó con dos imágenes en el registro: dessydevopsacr.azurecr.io/k8app-backend y dessydevopsacr.azurecr.io/k8app-frontend. Nada revolucionario en la estructura, pero es exactamente el tipo de detalle que la mayoría de los tutoriales “para principiantes” se saltan, con la excusa de simplificar.

Cómo se diseña la red privada para PostgreSQL: VNet, subred y CIDR

La regla dura es simple: ningún rango de subred puede solaparse con otro dentro de la misma VNet. Goldsmith reservó postgres-subnet con el bloque 10.0.2.0/24 exclusivamente para la base, separado de los rangos que usa AKS internamente para pods y servicios.

Acá hay un punto que confunde a mucha gente que recién arranca con redes en Azure: el Pod CIDR (10.244.0.0/16) y el Service CIDR (10.240.0.0/16) de AKS no son lo mismo que la subred de la VNet donde vive el clúster. Son rangos internos que Kubernetes usa para asignar IPs a pods y servicios, independientes del espacio de direcciones que se ve en el portal de Azure Networking.

El propio Goldsmith admite en su artículo que no guardó registro del CIDR exacto de la subred de AKS dentro de la VNet, así que no lo inventa ni lo estima. Esa honestidad metodológica es más valiosa que rellenar el hueco con un número que suena razonable pero que nadie verificó. Para más detalles técnicos, mirá certificaciones de Azure según tu rol.

AspectoPostgreSQL en pod de AKSPostgreSQL Flexible Server
BackupsLos configurás vos, con volúmenes y jobs propiosAutomáticos, gestionados por Azure
Patching del motorResponsabilidad tuyaAzure aplica actualizaciones
Impacto de un fallo de nodoRiesgo directo sobre la baseAislado del ciclo de vida del clúster
CostoSolo cómputo del clústerCómputo del clúster + servicio administrado (ej. Standard_B1ms)
Uso en este proyectoNo se usóSí, servidor dessy-k8app-postgres, PostgreSQL 16
pipeline ci/cd azure devops aks diagrama explicativo

Criterios de decisión: ¿cuándo conviene cada opción?

Ni la fuente ni este artículo tienen precios concretos para decidir por vos, pero del propio caso documentado se pueden sacar tres preguntas prácticas para orientar la elección:

  • ¿Quién va a operar el patching y los backups? Si no hay una persona o proceso dedicado a mantener PostgreSQL al día, un pod dentro de AKS termina siendo trabajo manual acumulado. Flexible Server saca esa carga de la lista de pendientes, al costo de perder algo de control fino sobre la configuración del motor.
  • ¿Qué tan crítico es el tiempo de inactividad de la base? En un proyecto de aprendizaje como este, un rato de downtime no rompe nada. En un servicio con usuarios reales, que la base dependa del mismo ciclo de vida que los pods de aplicación (reinicios, actualizaciones de nodos, rollouts) es un riesgo que la separación de Goldsmith evita directamente.
  • ¿El tráfico esperado excede lo que un SKU de entrada puede manejar? Standard_B1ms funcionó para este proyecto porque la carga era mínima. Antes de replicar la elección en algo con tráfico real, conviene revisar los límites de cómputo y conexiones concurrentes de ese SKU en la documentación oficial de Azure, no asumir que escala solo.

Qué rol cumple la zona DNS privada para que la app encuentre la base de datos

La zona DNS privada traduce el hostname del servidor PostgreSQL a su IP dentro de la red, algo imprescindible porque tener la IP privada correcta no sirve de nada si la aplicación no puede resolver el nombre. Goldsmith creó la zona private.postgres.database.azure.com y la vinculó a la VNet del proyecto.

El hostname completo del servidor, dessy-k8app-postgres.postgres.database.azure.com, resolvió puntualmente a la IP privada 10.0.2.4 dentro de esa subred. Sin ese vínculo entre la zona DNS y la VNet (el paso de “Virtual network links” en el portal), un pod dentro de AKS conocería el nombre pero no tendría forma de ubicarlo en la red.

Es la misma lógica que un teléfono guardado sin número: sabés a quién querés llamar, pero no hay forma de que la llamada salga.

Cómo se verifica desde AKS que la conexión a PostgreSQL funciona

Se verifica con un pod temporal que hace dos pruebas separadas: primero resuelve el DNS, después intenta la conexión real a la base. Goldsmith usó un pod llamado psql-test con la imagen postgres:16 para no depender de herramientas instaladas en su máquina local.

El primer comando fue kubectl run psql-test --rm -it --image=postgres:16 --restart=Never -- getent hosts dessy-k8app-postgres.postgres.database.azure.com, que confirmó la resolución hacia 10.0.2.4. Recién después de eso corrió el segundo comando, con psql apuntando al mismo host, puerto 5432, base app, usuario k8appadmin y sslmode=require. Cubrimos ese tema en detalle en diferencias entre Azure DevOps y GitHub.

¿Y si el primer comando falla? Entonces el problema es de red o DNS, no de credenciales, así que separar ambas pruebas ahorra tiempo de diagnóstico. Es un detalle metodológico chico pero que evita perder horas culpando a la contraseña cuando en realidad el pod ni siquiera puede ubicar el servidor en la red.

Cómo se conecta todo el flujo: de Azure DevOps a Helm y AKS

El flujo completo documentado arranca en GitHub, pasa por Azure DevOps para build y test, construye la imagen Docker, la empuja a ACR y termina con un despliegue vía Helm sobre namespaces separados de frontend y backend en AKS. El repositorio del proyecto, k8app-azure-devops, agrupa el código de la app junto con el chart de Helm y el pipeline azure-pipelines.yml.

Subís el código, Azure DevOps lo compila, lo empaqueta en un contenedor, lo manda a dessydevopsacr, Helm toma ese chart y lo aplica sobre AKS, el frontend queda expuesto por un LoadBalancer Service y el backend habla con la base por ClusterIP y red privada: todo ese recorrido, encadenado, es lo que Goldsmith llama arquitectura DEV, sin gates de aprobación ni ambientes de UAT o PROD todavía.

Ese último punto conviene marcarlo bien claro: el propio autor dice que UAT, PROD y approval gates “will be added later” (se agregarán después). No están implementados en esta versión del proyecto.

Qué está confirmado y qué queda pendiente

  • Confirmado: resource group devops-aks-lab-rg-sa en South Africa North, ACR dessydevopsacr con admin deshabilitado, subred postgres-subnet en 10.0.2.0/24, servidor PostgreSQL 16 dessy-k8app-postgres con SKU Standard_B1ms y resolución DNS validada hacia 10.0.2.4.
  • Confirmado: la conexión desde un pod de AKS a la base se probó de forma directa con psql-test y sslmode=require, con resultado exitoso.
  • Pendiente: el CIDR exacto de la subred de AKS dentro de la VNet, que el propio autor no registró en sus notas.
  • Pendiente: ambientes de UAT y PROD, además de approval gates, que según el artículo se sumarán en una etapa posterior.
  • Contexto: se trata de un proyecto de aprendizaje personal bajo suscripción Azure for Students, no de un caso documentado de producción empresarial.

Errores comunes al armar este tipo de pipeline

  • Solapar rangos CIDR entre subredes. Si la subred de PostgreSQL y algún otro segmento de la VNet comparten direcciones, Azure directamente no te deja crear el recurso o generás conflictos de ruteo difíciles de diagnosticar.
  • Dejar habilitada la cuenta admin de ACR. Es más rápido para probar, pero deja credenciales estáticas dando vueltas. Usar una service connection de Azure DevOps es el camino que siguió este proyecto.
  • Exponer PostgreSQL con acceso público. Habilitar el endpoint público en vez de la integración privada con VNet anula buena parte del sentido de tener subredes separadas.
  • Olvidarse de vincular la zona DNS privada a la VNet. Sin ese “Virtual network link”, la resolución del hostname simplemente no funciona, aunque la zona DNS exista.
  • Asumir que un fallo de conexión es de credenciales. Antes de tocar usuario y contraseña, conviene correr primero getent hosts para confirmar que el DNS resuelve. Si eso falla, el problema es de red, no de login.

Preguntas que quedan dando vueltas

Más que un listado de FAQ genérico, van tres dudas puntuales que probablemente surjan si estás por replicar algo parecido.

¿Alcanza con la VNet compartida y la zona DNS privada, o hace falta algo más para que AKS vea la base? Con eso alcanza, siempre que no te olvides del “Virtual network link” que conecta la zona DNS con la VNet. Es el paso que más fácil se salta porque el portal no lo fuerza de forma obvia, y sin él el hostname queda huérfano aunque la zona exista.

¿Vale la pena replicar exactamente este setup para un proyecto propio, o conviene adaptarlo? El esqueleto (subred dedicada, DNS privado, ACR sin admin) es reutilizable casi tal cual. Lo que no conviene copiar sin pensar es el SKU de PostgreSQL: Standard_B1ms le sirvió a Goldsmith porque el tráfico era mínimo, pero no es un número mágico para cualquier escenario.

¿Qué se pierde por no tener todavía UAT, PROD ni approval gates? Nada catastrófico para un entorno DEV de aprendizaje, pero sí una capa de seguridad que cualquier pipeline real necesita antes de tocar producción: revisión manual antes de promover un cambio, y un ambiente intermedio donde detectar problemas antes de que lleguen a usuarios reales.

Conclusión

Lo valioso de este pipeline CI/CD Azure DevOps AKS documentado por Goldsmith no es la app de ejemplo, es el mapa completo de decisiones de red y seguridad que casi ningún tutorial cubre en detalle: subredes sin solapar, DNS privado vinculado a la VNet, ACR sin credenciales estáticas y verificación de conectividad separada de la verificación de login.

Si estás armando algo parecido, el criterio práctico es simple: antes de tocar código de aplicación, probá la cadena de red de punta a punta con un pod descartable. Si getent hosts no resuelve, no pierdas tiempo revisando strings de conexión en tu app, el problema está un nivel más abajo. Y antes de copiar el SKU de PostgreSQL tal cual, revisá los criterios de decisión de arriba contra tu propio tráfico esperado, no contra el de un proyecto de estudiante.

Para hosting y dominios en Argentina, si en algún punto tu stack necesita infraestructura fuera de Azure, donweb.com es una opción a considerar.

Fuentes

Te puede interesar...