Clase 08 — curl en DevOps
curl en Docker
Health checks
# Dockerfile con healthcheck
FROM python:3.12-slim
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["python", "main.py"]
# docker-compose.yml con healthcheck
services:
api:
image: mi-api:latest
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
nginx:
image: nginx:latest
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 15s
timeout: 3s
retries: 5
depends_on:
api:
condition: service_healthy
Docker API
# Docker expone una API REST por socket Unix
# Listar contenedores
curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.[].Names'
# Info de un contenedor
curl -s --unix-socket /var/run/docker.sock \
http://localhost/containers/mi-api/json | jq '{
Name, State: .State.Status, Image: .Config.Image
}'
# Listar imágenes
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json | \
jq '.[] | {id: .Id[7:19], tags: .RepoTags, size_mb: (.Size / 1048576 | floor)}'
# Crear contenedor
curl -s -X POST --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
"http://localhost/containers/create?name=test-curl" \
-d '{
"Image": "nginx:latest",
"ExposedPorts": {"80/tcp": {}},
"HostConfig": {
"PortBindings": {"80/tcp": [{"HostPort": "9090"}]}
}
}' | jq '.'
# Iniciar contenedor
curl -s -X POST --unix-socket /var/run/docker.sock \
http://localhost/containers/test-curl/start
# Detener contenedor
curl -s -X POST --unix-socket /var/run/docker.sock \
http://localhost/containers/test-curl/stop
# Logs
curl -s --unix-socket /var/run/docker.sock \
"http://localhost/containers/test-curl/logs?stdout=true&tail=20"
curl en CI/CD
GitHub Actions
name: Deploy Pipeline
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: docker build -t mi-api:${{ github.sha }} .
- name: Smoke Test
run: |
for i in {1..30}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 \
"https://staging.mi-api.com/health")
if [[ "$STATUS" == "200" ]]; then
echo "✅ Servicio listo"
exit 0
fi
echo "Esperando... ($i/30)"
sleep 5
done
echo "❌ Timeout - servicio no responde"
exit 1
- name: Notify Result
if: always()
run: |
if [[ "${{ job.status }}" == "success" ]]; then
COLOR="#36a64f"; ICON="✅"; MSG="Deploy exitoso"
else
COLOR="#dc3545"; ICON="❌"; MSG="Deploy falló"
fi
curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg text "$ICON $MSG" \
--arg color "$COLOR" \
--arg repo "${{ github.repository }}" \
--arg sha "${{ github.sha }}" \
'{text: $text, attachments: [{color: $color, fields: [{title:"Repo",value:$repo},{title:"SHA",value:$sha}]}]}'
)"
GitLab CI
deploy:
stage: deploy
script:
- |
curl -s -X POST \
-H "Authorization: Bearer $DEPLOY_TOKEN" \
-H "Content-Type: application/json" \
"https://deploy.ejemplo.com/api/deploy" \
-d "{
\"app\": \"mi-api\",
\"version\": \"$CI_COMMIT_SHA\",
\"env\": \"production\"
}"
- |
for i in $(seq 1 20); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"https://api.ejemplo.com/health")
[ "$STATUS" = "200" ] && exit 0
sleep 5
done
exit 1
curl con Kubernetes
# API de Kubernetes (usando token de ServiceAccount)
K8S_API="https://kubernetes.default.svc"
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
# Listar pods
curl -s --cacert "$CACERT" -H "Authorization: Bearer $TOKEN" \
"$K8S_API/api/v1/namespaces/default/pods" | jq '.items[].metadata.name'
# Health check del cluster
kubectl proxy &
curl -s http://localhost:8001/healthz
curl -s http://localhost:8001/api/v1/nodes | jq '.items[].metadata.name'
curl con Terraform
# Terraform Cloud API
TF_TOKEN="$TFC_TOKEN"
TF_ORG="mi-org"
AUTH=(-H "Authorization: Bearer $TF_TOKEN" -H "Content-Type: application/vnd.api+json")
# Listar workspaces
curl -s "${AUTH[@]}" \
"https://app.terraform.io/api/v2/organizations/$TF_ORG/workspaces" | \
jq '.data[] | {name: .attributes.name, status: .attributes."current-run".status}'
# Trigger un plan
curl -s -X POST "${AUTH[@]}" \
"https://app.terraform.io/api/v2/runs" \
-d "{
\"data\": {
\"type\": \"runs\",
\"attributes\": {\"message\": \"Triggered via curl\"},
\"relationships\": {
\"workspace\": {
\"data\": {\"type\": \"workspaces\", \"id\": \"ws-xxx\"}
}
}
}
}" | jq '.data.id'
Notificaciones
Slack
#!/bin/bash
slack_send() {
local webhook="$SLACK_WEBHOOK"
local text="$1"
local color="${2:-#36a64f}"
curl -s -X POST "$webhook" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg text "$text" \
--arg color "$color" \
'{attachments: [{color: $color, text: $text, footer: "DevOps Bot"}]}'
)"
}
slack_send "✅ Deploy v2.1.0 completado en producción" "#36a64f"
slack_send "❌ Pipeline falló en stage test" "#dc3545"
Discord
discord_send() {
local webhook="$DISCORD_WEBHOOK"
local titulo="$1"
local mensaje="$2"
local color="${3:-3066993}"
curl -s -X POST "$webhook" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg title "$titulo" \
--arg desc "$mensaje" \
--argjson color "$color" \
'{embeds: [{title: $title, description: $desc, color: $color}]}'
)"
}
discord_send "Deploy Exitoso" "Versión 2.1.0 desplegada en producción" 3066993
Telegram
telegram_send() {
local token="$TELEGRAM_BOT_TOKEN"
local chat_id="$TELEGRAM_CHAT_ID"
local mensaje="$1"
curl -s -X POST "https://api.telegram.org/bot${token}/sendMessage" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg chat "$chat_id" \
--arg text "$mensaje" \
'{chat_id: $chat, text: $text, parse_mode: "Markdown"}'
)"
}
telegram_send "🚀 *Deploy exitoso*\nApp: pokemon-api\nVersión: 2.1.0"
Ejemplo: Script de deploy completo
#!/bin/bash
set -euo pipefail
APP="pokemon-api"
VERSION="${1:?Uso: $0 <version>}"
HEALTH_URL="https://api.ejemplo.com/health"
log() { echo "[$(date '+%H:%M:%S')] $*"; }
fail() { log "❌ $*"; slack_send "❌ Deploy $APP v$VERSION falló: $*" "#dc3545"; exit 1; }
# 1. Verificar que la imagen existe
log "Verificando imagen Docker..."
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $REGISTRY_TOKEN" \
"https://ghcr.io/v2/$APP/manifests/$VERSION")
[[ "$http_code" != "200" ]] && fail "Imagen $APP:$VERSION no encontrada"
# 2. Notificar inicio
log "Iniciando deploy..."
slack_send "🚀 Iniciando deploy de $APP v$VERSION" "#2196F3"
# 3. Esperar que el servicio esté saludable
log "Esperando servicio..."
for i in {1..40}; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$HEALTH_URL")
if [[ "$code" == "200" ]]; then
log "✅ Servicio saludable"
break
fi
[[ $i -eq 40 ]] && fail "Timeout esperando servicio"
sleep 5
done
# 4. Smoke tests
log "Ejecutando smoke tests..."
test_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
"https://api.ejemplo.com/v2/pokemon/pikachu")
[[ "$test_code" != "200" ]] && fail "Smoke test falló (HTTP $test_code)"
# 5. Notificar éxito
log "✅ Deploy completado exitosamente"
slack_send "✅ Deploy $APP v$VERSION completado en producción" "#36a64f"
Ejercicios
- Creá un Dockerfile con healthcheck que use curl
- Escribí un script que notifique a Slack el resultado de un deploy
- Usá la Docker API (socket Unix) para listar contenedores y su estado
- Creá un smoke test para un servicio web que verifique múltiples endpoints
- Implementá un script de deploy que: verifique la imagen, despliegue, espere, y notifique