Un pipeline simple, pero efectivo. 😉

Un pipeline simple, pero efectivo. 😉

Construimos un CI/CD con GitHub Actions, DockerHub & ArgoCD. Una manera sencilla de desplegar nuestras aplicaciones.

Introducción

Hace tiempo que no actualizo los proyectos DevOps del Blog, así que vamos a crear algo sencillo pero eficaz para aquellos que están haciendo sus primeras armas en este mundo.

Les comparto este proyecto para que puedan desplegarlo fácilmente en sus entornos. Un pipeline completo. Explicaremos cómo crear una imagen Docker y subirla a Docker Hub, utilizar un Helm Chart y modificarlo, automatizar estos procesos con GitHub Actions, para finalmente desplegarlo en nuestro Kubernetes via ArgoCD.

Aca el contenido del proyecto, pero tranquilos que vamos a ir paso a paso.

Creación de Imagen Docker

Vamos a crear local nuestra imagen docker, solo para probar que el Dockerfile está correcto y luego correrla en el puerto 8080.

¡Todo perfecto!

Repasamos los comandos.

# Construccion de Imagen, debemos estar en la carpeta app donde encontramos el Dockerfile
docker build . -t app
# Listar Imagenes
docker images
# Levantar el Contenedor 
docker run -it --rm -d -p 8080:80 app
# Status del Contenedor
docker ps

El primer paso está correcto. ¡Seguimos!

Despliegue en Kubernetes

Vamos a revisar los manifiestos y desplegar. En este caso ya tenemos la imagen en DockerHub, para esta prueba, algo que luego hara GitHub Actions no solo sera el build, si no también el push al registro.

Vamos a repasar los comandos.

# Creo el Namespace app
kubectl create namespace app
# Aplico los manifiestos
kubectl apply -f .
# Revisamos el Ingress
kubectl get ingress -n app
# Listamos los artefactos del Namespace app
kubectl get all -n app

¿Le pegamos una mirada a cada manifiesto, dentro de k8s/manifest?

Primero el deployment.yaml que referencia a la imagen subida a DockerHub.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: app
  labels:
    app: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: safernandez666/app # Cambiar por tu imagen en DockerHub
        ports:
        - containerPort: 80

Segundo el service.yaml.

apiVersion: v1
kind: Service
metadata:
  name: app
  namespace: app
  labels:
    app: app
spec:
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: app
  type: ClusterIP

Y por último el ingress.yaml, que referencia la FQDN que queremos resolver.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: app.esprueba.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app
            port:
              number: 80

⚠️ Es necesario que tengamos desplegado ingress en nuestro cluster.

La resolucion, para esta prueba, del FQDN la hice modificando mis registro local apuntando a mi cluster de Kubernetes. Dependiendo de la plataforma se hace de diferente manera, si estás tocando Clusters seguramente sepas hacerlo. Si no sabes me avisas 📞 y te guio.

CI con GitHub Actions

Vamos al corazón de esta automatización. El manifiesto de GitHub Actions. Antes que nada vamos a sumar los secretos de DockerHub en las variables. Para ello en tu repositorio, debes hacer lo siguiente.

Vamos a cargar las variables de inicio para el login en DockerHub, una vez cargados los secretos, vamos modificar los permisos de GITHUB_TOKEN para poder escribir, desde la GitHub Actions en el YAML del Helm Chart.

fix GitHub bot permission

Ahora si revisamos el Workflow que se puede generar desde acá.

name: Build and Push Docker Image to Docker Hub

on: 
  push:
    branches:
      - master
    paths-ignore: # Si realizamos cambios, aqui, no se dispara el pipeline.
      - 'k8s/**'
      - 'README.md'

jobs:
  build-and-update: # Construccion de la Imagen & Push en DockerHub
    name: Build Image and Update Helm Chart
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
        # Agregamos el TAG para subir no solo latest, si no tambien la version.
      - name: Extract version number from GIT SHA
        id: extract_sha
        run: echo "BUILD_TAG=${GITHUB_SHA::7}" >> $GITHUB_ENV

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: app/
          push: true
          tags: safernandez666/app:latest, safernandez666/app:${{ env.BUILD_TAG }}

      - name: Update tag in Helm Chart # Cambiamos por el numero de version el values.yaml
        run: |
          sed -i 's/tag: .*/tag: "${{ env.BUILD_TAG }}"/' app-chart/values.yaml

      - name: Commit and push changes
        run: |
          git config --global user.email "santiagoagustinfernandez@gmail.com"
          git config --global user.name "Santiago Fernandez"
          git add app-chart/values.yaml
          git commit -m "Update tag to '${{ env.BUILD_TAG }}' after building image"
          git push
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Usando el token para autenticar el push

Perfecto, vamos a realizar un cambio para ver como interactua. Modificamos el archivo values.yaml para actualizar la etiqueta con el número de versión después de construir la imagen. Ahora ponemos latest solo para ver el cambio 😎.

¡Hacemos el commit!

¡Excelente!

Se está realizando el Build y Subiendo a DockerHub. ¡Miremos le Job a detalle! Se modifico el archivo app-chart/values.yaml con el tag:4044587.

Revisemos el archivo del Helm Chart y el registro.

Todo está funcionando como queremos. Ahora solo nos queda instalar ArgoCD para desplegar la versión que deseamos.

CD con ArgoCD

¿Que es ArgoCD? No es ni más ni menos que una herramienta de despliegue continuo que nos permite automatizar el despliegue y la gestión de sus aplicaciones utilizando Git como una única fuente de verdad

Instalación de ArgoCD

Creamos el Namespace y descargamos los manifiestos.

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Ahora nos queda, para facilitar el ingreso, cambiar el servicio por LoadBalancer.

kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
kubectl get svc argocd-server -n argocd

Obtenemos la dirección IP y por último necesitar el password para el usuario admin, con este comando podemos conocerlo.


kubectl get svc argocd-server -n argocd
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

Configuración de ArgoCD

Conectamos nuestro repositorio de GitHub.

Creamos un proyecto donde configuramos la fuente y con qué Cluster vamos a trabajar.

Ahora el paso final, crear la aplicación con algunas configuraciones básicas.

¡Uala! Ya tenemos el aplicativo sincronizado.

Cada vez que ArgoCD detecte un cambio en el repositorio hará el despliegue del Helm Chart, con la última versión de build que generamos.

Te dejo un video, para que lo veas en acción.

Espero que te sirva para comenzar en el mundo DevOps.

Saludos.