DevOps i Cloud Computing: La marató definitiva d'automatització, CI/CD i Observabilitat a AWS

DevOps i Cloud Computing: La marató definitiva d'automatització, CI/CD i Observabilitat a AWS

Si les assignatures de Sistemes Operatius i Xarxes us van semblar intenses, prepareu-vos bé perquè DevOps i Cloud Computing ha estat la joia de la corona; una autèntica marató tècnica que et canvia la perspectiva per complet. Aquí ja no s'hi val a dir allò de *"a la meva màquina funciona"*. Si no està automatitzat, si no passa el linter, si no es monitoritza en temps real... simplement no existeix.

Vull compartir amb vosaltres tot el camí recorregut al llarg d'aquest semestre a la UOC-Jesuïtes (2026), desglossant els tres grans "Productes" pràctics que vam haver de suar, configurar i defensar. Des de picar codi en Go i empaquetar-lo en contenidors minimalistes, fins a muntar un pipeline complet de CI/CD que automatitza les revisions de codi a GitHub, acabant amb el desplegament d'infraestructura al núvol monitoritzada fins a l'últim byte. Som-hi!


1. Producte 1: Preparació de l'Entorn (Go 1.25 + Docker Multi-Stage)

El primer gran repte va ser posar les bases de la filosofia DevOps: aïllament, consistència i eficiència. En lloc de barallar-nos amb configuracions locals pesades que acaben embrutant el sistema operatiu, ens vàrem proposar desenvolupar i empaquetar un microservei web utilitzant Go 1.25 i Docker.


package main
import (
	"fmt"
	"log"
	"net/http"
	"os"
)
func main() {
	// Port configurable mitjançant una variable d'entorn (útil a Docker)
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	mux := http.NewServeMux()
	// 1) Serveix la carpeta /static/ (imatges i altres recursos estàtics)
	fs := http.FileServer(http.Dir("./static"))
	mux.Handle("/static/", http.StripPrefix("/static/", fs))
	// 2) Endpoint principal
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Bones pràctiques: comprovar el mètode permès i el tipus de contingut
		if r.Method != http.MethodGet {
			http.Error(w, "Método no permitido", http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		fmt.Fprint(w, `
<!DOCTYPE html>
<html lang="es">
<head>
	<meta charset="UTF-8">
	<title>Producto 1 - UOC</title>
</head>
<body style="text-align:center;">
	<h1>Soy alumno de la UOC (davidov@uoc.edu)</h1>
	<p>Producto 1: entorno Go + Docker</p>
	<img src="/static/uoc.jpg" alt="Imagen UOC" width="900">
</body>
</html>
`)
	})
	// 3) Endpoint opcional per comprovar l'estat del servei (health check)
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		fmt.Fprint(w, "OK")
	})
	srv := &http.Server{
		Addr:    ":" + port,
		Handler: logRequest(mux),
	}
	log.Printf("Servidor escoltant a http://localhost:%s", port)
	log.Fatal(srv.ListenAndServe())
}
// Middleware senzill per registrar les peticions rebudes
func logRequest(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

Com que era el nostre primer contacte amb l'entorn real de contenidors, vàrem decidir enfocar el desenvolupament de forma col·laborativa però organitzada. Vam centralitzar tota la documentació tècnica en un espai de Notion del grup i estructurar el repositori de GitHub amb carpetes individuals específiques per a cadascú de nosaltres (`alex`, `andrey`, `davidov`).

L'Enfocament del Contenidor Eficient (Multi-Stage Build)

Un dels errors més comuns en començar amb Docker és generar imatges gegantines (de centenars de megabytes o fins i tot gigues) que inclouen tot l'SDK, compiladors i eines de depuració en la imatge que finalment anirà a producció. Per solucionar-ho, implementarem una estratègia professional de Construcció Multi-Etapa (Multi-Stage Build).

La idea és senzilla:

1. Fase de Construcció (Builder): Utilitzem una imatge robusta de Go basada en Alpine per compilar el binari executable.

2. Imatge Final (Runtime): Copiem únicament l'executable resultant a una imatge completament neta i ultralleugera d'Alpine Linux. El resultat final és una imatge de només 5MB!

Aquí teniu l'estructura exacta i polida del nostre `Dockerfile` de producció:


# ETAPA 1: Compilació (aquí definim l'executable)
# Versió de GO i directori de treball
FROM golang:1.25 AS builder
WORKDIR /app
# Copiem els fitxers de gestió de llibreries
COPY go.mod ./
RUN go mod download
# Copiem la resta del codi i creem el fitxer "server" (BUILD PESAT)
# Aquesta imatge de “debug” no està pensada per fer deploy, és el codi en “brut”
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server main.go
# ETAPA 2: Imatge final (l'executable/runtime lleuger que enviem a producció)
# Podríem dir que és el microservei pensat i optimitzat per fer deploy, minimalista
# Alpine és un “miniLinux” d'uns 5 Mb al qual li afegim el codi del nostre servei
FROM alpine:3.20
WORKDIR /app
# Només ens portem el necessari de l'etapa anterior: llibreries, dependències i codi
# mínim, així com els fitxers estàtics (fonts, scripts, imatges, css…)
COPY --from=builder /app/server /app/server
COPY --from=builder /app/static /app/static
# Avisem que el contenidor utilitzarà el port 8081 (port definit per a GO)
EXPOSE 8081
ENTRYPOINT ["/app/server"]

Verificació de l'Entorn Local

En desplegar aquest microservei en local mapejant el port 8081, aconseguirem aixecar la nostra aplicació web en qüestió de mil·lisegons amb un consum de recursos mínim. L'execució i l'aïllament seran perfectes, demostrant que podíem compilar codi sense necessitat de tenir l'SDK instal·lat directament a la màquina amfitriona.

Descarregar: Producte 1 - Entorn Go + Docker


2. Producte 2: El Cor de la CI/CD (AWS = Jenkins + Minikube + GitHub Integration)

El clímax i veritable "mal de cap" de l'assignatura va arribar amb el Producte 2. Ens vam acomiadar de l'entorn local controlat i ens vàrem mudar a la infraestructura real del núvol a Amazon Web Services (AWS) per desplegar el nostre servei en una instància EC2 instal·lant Jenkins, Minikube i tot el necessari per començar el nostre desenvolupament al núvol.

Amb l'aplicació ja contenedoritzada, el pas lògic era automatitzar el cicle de vida del codi. El Producte 2 ens endinsa en el veritable flux d'integració i desplegament continuat (CI/CD) connectant Jenkins amb GitHub Webhooks i orquestrant el desplegament local mitjançant Kubernetes (Minikube).

###La metodologia aplicada va ser 100% professional:

Ens proposem treballar estrictament amb branques individuals (`alex-branch`, `andrey-branch`, `davidov-branch`) i prohibim totalment els commits directes a `main`. Qualsevol canvi ha de ser auditat automàticament per la màquina abans que un humà (jo! 😉) li doni el vist i plau.

El Flux de Validació del Pipeline

Ara toca dissenyar un arxiu automatitzat `Jenkinsfile` que reaccioni immediatament a qualsevol `git push` a les branques de desenvolupament. El flux seguirà aquests passos rigorosos:

1. Fase de Linting (Validació): Jenkins analitza la sintaxi i l'estructura del codi base (HTML i Go). Per posar a prova el sistema i comprovar que funcionava, simularem un error real introduint una etiqueta de paràgraf `<p>` mal tancada. Jenkins ha de saltar immediatament marcant el commit amb una X vermella a la interfície de GitHub i va bloquejant automàticament qualsevol possibilitat d'avançar.

2. Creació de Pull Request: Si el codi passa el linter de forma neta (check verd), el desenvolupador sap que el seu codi és "bo" i pot obrir un *Pull Request* cap a la branca `main`.

3. Revisió i Merge: L'administrador del repositori avaluarà visualment l'estat del pipeline a GitHub. Si veu el check verd de Jenkins, aprova la fusió fent el *Merge*.

4. Desplegament Automàtic a Kubernetes: El merge dispara la fase final del pipeline a la branca principal (`main`). Jenkins compila la versió definitiva, genera la imatge Docker, li aplica el tag de `latest` i actualitza automàticament els pods al nostre clúster de Minikube exposant el servei al port 8081.

Diagrama del Flux CI/CD amb Jenkins i Kubernetes

Resum de l'arquitectura del flux de treball en equip

Rol / QuiAcció RealitzadaEntorn / On passa
DesenvolupadorEscriu codi, fa commit i `git push`A la seva branca personal (`davidov-branch`)
Jenkins ServerAutomatitza el Linter i proves estàtiquesNotifica amb Check o X directament a GitHub
DesenvolupadorSol·licita la fusió mitjançant *Pull Request*Interfície web de GitHub
Admin GitHubRevisa els checks d'automatització i fa *Merge*Repositori central de GitHub
Jenkins (Final)Compilació global, Docker Build i DeployDesplegament de Pods/Services a Minikube

Descarregar: PIPELINE ADMIN GITHUB

Descarregar: Producte 2 - CI/CD Jenkins + Minikube


3. Producte 3: Infraestructura al Núvol i Observabilitat Activa (AWS + Prometheus + Grafana + Loki)

Ara toca veure que tot funciona de forma eficient ;) anem a monitorar...

L'observabilitat és clau en producció: Les mètriques pures sense visualització o les alertes sense llindars lògics no eviten les caigudes. La combinació de Prometheus per a mètriques i Loki per a logs consolida un diagnòstic ràpid.

Recollida de Mètriques amb Prometheus i Node Exporter

Per saber amb precisió quirúrgica què passa al servidor, vàrem instal·lar Node Exporter directament al sistema operatiu de la instància EC2. Aquest agent recol·lecta mètriques pures de maquinari (ús de CPU, xarxa, lectura de disc, memòria). Posteriorment, toca configurar Prometheus per realitzar el raspallat (*scrape*) d'aquestes dades en intervals específics.

Monitorització del Servidor amb Prometheus

Arxiu de configuració definitiu de `prometheus.yml`:


global:
  scrape_interval: 15s
  evaluation_interval: 15s
rule_files:
  - "alert_rules.yml"
scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9091"]
  - job_name: "node"
    static_configs:
      - targets: ["localhost:9100"] # Endpoint d'escolta de Node Exporter

Gestió d'Alertes Intel·ligents

Tenir gràfiques boniques no serveix de res si has d'estar mirant-les les 24 hores del dia. Per això, cal dissenyar un fitxer de regles d'alerta robust (`alert_rules.yml`) amb llindars crítics calculats mitjançant expressions matemàtiques perquè el sistema ens avisi si la infraestructura perilla:


groups:
  - name: servidor_alertes
    rules:
      - alert: ServidorCaido
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Servidor caigut"
          description: "Prometheus no pot contactar amb l'objectiu de monitorització. El servei web està fora de línia!"
      - alert: CPUAlta
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU superior al 80%"
          description: "Ús elevat i sostingut del processador detectat a la instància cloud EC2."
      - alert: MemoriaAlta
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Memòria superior al 85%"
          description: "La memòria RAM disponible està assolint límits crítics. Perill d'OOM Killer."

El Panell de Control: Grafana i Anàlisi de Logs amb Loki + Promtail

Amb les mètriques fluint cap a Prometheus, les connectarem com a origen de dades a Grafana, dissenyant un Dashboard visual unificat interactiu per veure l'estat de la màquina d'un sol cop d'ull.

Dashboard de Grafana amb mètriques i pSQL

No obstant això, les mètriques només et diuen *quan* falla alguna cosa, però no *per què*. Per solucionar l'altra meitat del problema de l'observabilitat, cal implementar Loki (l'agregador de logs) juntament amb Promtail (l'agent encarregat de llegir els fitxers de logs del microservei en temps real). Gràcies a aquesta integració, podem realitzar auditories forenses completes: si veiem un pic anòmal de CPU als panells de Grafana, podem seleccionar aquell rang de temps exacte i veure les línies d'error detallades que la nostra aplicació en Go ha registrat en aquell mateix segon.

Descarregar: Producte 3 - Prometheus + Grafana + Loki


Conclusió i Reflexions Finals

Aquesta assignatura ha estat una autèntica immersió de realitat. DevOps no va d'utilitzar eines modernes perquè estiguin de moda; va de crear ponts sòlids, automatitzats, reproduïbles i segurs entre el desenvolupament de programari i les operacions de sistemes. He passat de gestionar contenidors senzills en local a entendre l'orquestració avançada i com dormir tranquil a les nits gràcies a un stack de monitorització proactiu al núvol.

Si heu arribat fins aquí llegint tot aquest "totxo tècnic", us ho dic de debò: us mereixeu un premi! 🏆 Com que m'apassiona compartir coneixement i vull que veieu com defensem tot això davant del tribunal, us deixo aquí l'accés al vídeo complet de la nostra presentació del projecte:

¿Teniu algun dubte sobre com configurar el vostre Jenkinsfile, gestionar les alertes matemàtiques de Prometheus o barallar-vos amb les polítiques de ports de seguretat a AWS EC2? Deixeu un comentari a sota i farem un debug o un `git log` junts!

Comentaris