Fundamentos del Desarrollo Cloud Native: Kubernetes, Docker, Contenedores, Serverless y Arquitectura en la Nube
Este artículo es una introducción completa a los fundamentos del desarrollo Cloud Native, incluyendo Kubernetes, Docker, contenedores, Serverless y arquitectura en la nube con ejemplos prácticos.
En pocas palabras
El desarrollo Cloud Native es un enfoque para construir y operar aplicaciones que aprovecha plenamente los modelos de computación en la nube, creando sistemas escalables, resilientes y fáciles de mantener.
Descripción técnica concisa
Cloud Native es una metodología para desarrollar aplicaciones basadas en contenedores, microservicios, orquestación dinámica y entrega continua.
Componentes principales:
Containerización
- Docker: Estándar de facto para contenedores
- Container Images: Paquetes de aplicación inmutables y portátiles
- Container Registries: Docker Hub, AWS ECR, Google GCR
- Multi-Stage Builds: Creación optimizada de imágenes
Orquestación con Kubernetes
- Pods: Unidad desplegable más pequeña
- Services: Abstracción de red para Pods
- Deployments: Gestión de réplicas de Pods
- ConfigMaps & Secrets: Gestión de configuración
Computación Serverless
- Function as a Service (FaaS): Funciones orientadas a eventos
- Backend as a Service (BaaS): Servicios backend gestionados
- Event-Driven Architecture: Comunicación asincrónica
- Pay-per-Use: Facturación basada en consumo
Patrones Cloud Native
- Sidecar Pattern: Contenedores adicionales en el Pod
- Ambassador Pattern: API Gateway en contenedor
- Adapter Pattern: Estandarización de interfaces
- Init Containers: Inicialización previa
Puntos clave para evaluación
- Cloud Native: Desarrollo de aplicaciones optimizado para la nube
- Containers: Entornos de aplicación aislados
- Kubernetes: Plataforma de orquestación de contenedores
- Serverless: Arquitectura orientada a eventos con pago por uso
- Microservices: Servicios pequeños y autónomos
- CI/CD: Integración e implementación continua
- Immutable Infrastructure: Infraestructura inmutable
- Observability: Monitoreo, logging y tracing
- Relevante para la industria: Desarrollo y arquitectura moderna en la nube
Componentes clave
- Containers: Docker, Container Images, Registries
- Orquestación: Kubernetes, Services, Deployments
- Serverless: FaaS, Event-Driven Architecture
- CI/CD: Builds e implementaciones automatizados
- Monitoring: Prometheus, Grafana, OpenTelemetry
- Security: mTLS, RBAC, Secrets Management
- Networking: Service Mesh, Ingress, Load Balancing
- Storage: Persistent Volumes, Storage Classes
Ejemplos prácticos
1. Aplicación Cloud Native con Kubernetes
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloud-native-app
labels:
app: cloud-native-app
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: cloud-native-app
template:
metadata:
labels:
app: cloud-native-app
version: v1
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
containers:
- name: app
image: cloud-native-app:1.0.0
ports:
- containerPort: 8080
name: http
- containerPort: 9090
name: metrics
env:
- name: PORT
value: "8080"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log-level
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
volumeMounts:
- name: config-volume
mountPath: /app/config
readOnly: true
- name: cache-volume
mountPath: /app/cache
- name: sidecar
image: logging-sidecar:1.0.0
env:
- name: LOG_PATH
value: "/app/logs"
volumeMounts:
- name: log-volume
mountPath: /app/logs
initContainers:
- name: init-db
image: db-migrator:1.0.0
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
command: ["/app/migrate"]
volumes:
- name: config-volume
configMap:
name: app-config
- name: cache-volume
emptyDir:
sizeLimit: 100Mi
- name: log-volume
emptyDir:
sizeLimit: 50Mi
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
nodeSelector:
cloud.google.com/gke-nodepool: application-pool
tolerations:
- key: "workload"
operator: "Equal"
value: "application"
effect: "NoSchedule"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cloud-native-app
topologyKey: kubernetes.io/hostname
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: cloud-native-app-service
labels:
app: cloud-native-app
spec:
type: ClusterIP
selector:
app: cloud-native-app
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
- name: metrics
port: 9090
targetPort: 9090
protocol: TCP
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloud-native-app-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloud-native-app-service
port:
number: 80
---
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: cloud-native-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: cloud-native-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
log-level: "info"
feature-flags: |
{
"new-ui": true,
"beta-features": false,
"debug-mode": false
}
app.properties: |
server.port=8080
server.servlet.context-path=/api
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: <base64-encoded-database-url>
api-key: <base64-encoded-api-key>
jwt-secret: <base64-encoded-jwt-secret>
---
# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: default
---
# rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: default
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-role-binding
namespace: default
subjects:
- kind: ServiceAccount
name: app-service-account
namespace: default
roleRef:
kind: Role
name: app-role
apiGroup: rbac.authorization.k8s.io
---
# networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
spec:
podSelector:
matchLabels:
app: cloud-native-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: TCP
port: 53
port: 443
2. Función Serverless con AWS Lambda
# lambda_function.py
import json
import os
import logging
import boto3
from datetime import datetime
from typing import Dict, Any
import asyncio
from functools import wraps
# Configure logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Initialize AWS clients
dynamodb = boto3.resource('dynamodb')
s3 = boto3.client('s3')
sns = boto3.client('sns')
stepfunctions = boto3.client('stepfunctions')
# Environment variables
TABLE_NAME = os.environ.get('TABLE_NAME', 'serverless-app')
BUCKET_NAME = os.environ.get('BUCKET_NAME', 'serverless-bucket')
SNS_TOPIC_ARN = os.environ.get('SNS_TOPIC_ARN')
STEP_FUNCTION_ARN = os.environ.get('STEP_FUNCTION_ARN')
# Initialize DynamoDB table
table = dynamodb.Table(TABLE_NAME)
class LambdaContext:
"""Lambda context for better type hints"""
def __init__(self, aws_request_id: str, function_name: str, function_version: str):
self.aws_request_id = aws_request_id
self.function_name = function_name
self.function_version = function_version
def error_handler(func):
"""Decorator for error handling and logging"""
@wraps(func)
def wrapper(event, context):
try:
logger.info(f"Function {context.function_name} invoked with request ID: {context.aws_request_id}")
logger.info(f"Event: {json.dumps(event)}")
result = func(event, context)
logger.info(f"Function completed successfully")
return result
except Exception as e:
logger.error(f"Error in function {context.function_name}: {str(e)}")
logger.error(f"Event: {json.dumps(event)}")
# Send error notification
if SNS_TOPIC_ARN:
send_error_notification(str(e), context.aws_request_id)
# Return error response
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'error': 'Internal server error',
'message': str(e),
'requestId': context.aws_request_id
})
}
return wrapper
def cors_headers(func):
"""Decorator for CORS headers"""
@wraps(func)
def wrapper(event, context):
result = func(event, context)
if isinstance(result, dict) and 'statusCode' in result:
headers = result.get('headers', {})
headers.update({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
})
result['headers'] = headers
return result
return wrapper
def validate_request(required_fields):
"""Decorator for request validation"""
def decorator(func):
@wraps(func)
def wrapper(event, context):
if 'body' in event and event['body']:
try:
body = json.loads(event['body'])
except json.JSONDecodeError:
return {
'statusCode': 400,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'Invalid JSON in request body'})
}
else:
body = {}
# Validate required fields
missing_fields = [field for field in required_fields if field not in body]
if missing_fields:
return {
'statusCode': 400,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'error': 'Missing required fields',
'missing_fields': missing_fields
})
}
event['parsed_body'] = body
return func(event, context)
return wrapper
return decorator
@cors_headers
@error_handler
def lambda_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""Main Lambda handler"""
http_method = event.get('httpMethod')
path = event.get('path')
# Route based on HTTP method and path
if http_method == 'GET' and path == '/health':
return health_check(event, context)
elif http_method == 'GET' and path == '/items':
return list_items(event, context)
elif http_method == 'GET' and path.startswith('/items/'):
item_id = path.split('/')[-1]
return get_item(event, context, item_id)
elif http_method == 'POST' and path == '/items':
return create_item(event, context)
elif http_method == 'PUT' and path.startswith('/items/'):
item_id = path.split('/')[-1]
return update_item(event, context, item_id)
elif http_method == 'DELETE' and path.startswith('/items/'):
item_id = path.split('/')[-1]
return delete_item(event, context, item_id)
else:
return {
'statusCode': 404,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'Not found'})
}
def health_check(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""Health check endpoint"""
try:
# Check DynamoDB connectivity
table.load()
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'status': 'healthy',
'timestamp': datetime.utcnow().isoformat(),
'function': context.function_name,
'version': context.function_version,
'services': {
'dynamodb': 'connected',
's3': 'connected'
}
})
}
except Exception as e:
return {
'statusCode': 503,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'status': 'unhealthy',
'error': str(e),
'timestamp': datetime.utcnow().isoformat()
})
}
@cors_headers
@error_handler
def list_items(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""List all items"""
try:
# Parse query parameters
query_params = event.get('queryStringParameters', {}) or {}
limit = int(query_params.get('limit', 10))
offset = int(query_params.get('offset', 0))
# Scan DynamoDB table
response = table.scan(
Limit=limit,
ExclusiveStartKey={'id': str(offset)} if offset > 0 else None
)
items = response.get('Items', [])
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'items': items,
'count': len(items),
'last_evaluated_key': response.get('LastEvaluatedKey')
})
}
except Exception as e:
logger.error(f"Error listing items: {str(e)}")
raise
@cors_headers
@error_handler
@validate_request(['name', 'description'])
def create_item(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""Create a new item"""
try:
body = event['parsed_body']
# Generate item ID
item_id = str(int(datetime.utcnow().timestamp() * 1000))
# Create item object
item = {
'id': item_id,
'name': body['name'],
'description': body['description'],
'created_at': datetime.utcnow().isoformat(),
'updated_at': datetime.utcnow().isoformat(),
'status': 'active'
}
# Save to DynamoDB
table.put_item(Item=item)
# Trigger step function if needed
if STEP_FUNCTION_ARN:
trigger_step_function(item_id, 'created')
# Send SNS notification
if SNS_TOPIC_ARN:
send_notification(f"Item created: {item_id}", item)
return {
'statusCode': 201,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(item)
}
except Exception as e:
logger.error(f"Error creating item: {str(e)}")
raise
@cors_headers
@error_handler
def get_item(event: Dict[str, Any], context: LambdaContext, item_id: str) -> Dict[str, Any]:
"""Get item by ID"""
try:
response = table.get_item(
Key={'id': item_id}
)
item = response.get('Item')
if not item:
return {
'statusCode': 404,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'Item not found'})
}
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(item)
}
except Exception as e:
logger.error(f"Error getting item: {str(e)}")
raise
@cors_headers
@error_handler
@validate_request(['name', 'description'])
def update_item(event: Dict[str, Any], context: LambdaContext, item_id: str) -> Dict[str, Any]:
"""Update item by ID"""
try:
body = event['parsed_body']
# Update expression
update_expression = "SET #n = :name, description = :description, updated_at = :updated_at"
expression_attribute_names = {'#n': 'name'}
expression_attribute_values = {
':name': body['name'],
':description': body['description'],
':updated_at': datetime.utcnow().isoformat()
}
response = table.update_item(
Key={'id': item_id},
UpdateExpression=update_expression,
ExpressionAttributeNames=expression_attribute_names,
ExpressionAttributeValues=expression_attribute_values,
ReturnValues="ALL_NEW"
)
item = response.get('Attributes')
if not item:
return {
'statusCode': 404,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'Item not found'})
}
# Trigger step function if needed
if STEP_FUNCTION_ARN:
trigger_step_function(item_id, 'updated')
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(item)
}
except Exception as e:
logger.error(f"Error updating item: {str(e)}")
raise
@cors_headers
@error_handler
def delete_item(event: Dict[str, Any], context: LambdaContext, item_id: str) -> Dict[str, Any]:
"""Delete item by ID"""
try:
response = table.delete_item(
Key={'id': item_id},
ReturnValues="ALL_OLD"
)
item = response.get('Attributes')
if not item:
return {
'statusCode': 404,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': 'Item not found'})
}
# Trigger step function if needed
if STEP_FUNCTION_ARN:
trigger_step_function(item_id, 'deleted')
# Send SNS notification
if SNS_TOPIC_ARN:
send_notification(f"Item deleted: {item_id}", item)
return {
'statusCode': 204,
'headers': {'Content-Type': 'application/json'},
'body': ''
}
except Exception as e:
logger.error(f"Error deleting item: {str(e)}")
raise
def send_notification(message: str, item: Dict[str, Any]) -> None:
"""Send SNS notification"""
try:
sns.publish(
TopicArn=SNS_TOPIC_ARN,
Message=json.dumps({
'message': message,
'item': item,
'timestamp': datetime.utcnow().isoformat()
}),
Subject='Serverless App Notification'
)
except Exception as e:
logger.error(f"Error sending notification: {str(e)}")
def send_error_notification(error: str, request_id: str) -> None:
"""Send error notification"""
try:
sns.publish(
TopicArn=SNS_TOPIC_ARN,
Message=json.dumps({
'error': error,
'request_id': request_id,
'timestamp': datetime.utcnow().isoformat()
}),
Subject='Serverless App Error'
)
except Exception as e:
logger.error(f"Error sending error notification: {str(e)}")
def trigger_step_function(item_id: str, action: str) -> None:
"""Trigger Step Function"""
try:
stepfunctions.start_execution(
stateMachineArn=STEP_FUNCTION_ARN,
name=f"{item_id}-{action}-{int(datetime.utcnow().timestamp())}",
input=json.dumps({
'item_id': item_id,
'action': action,
'timestamp': datetime.utcnow().isoformat()
})
)
except Exception as e:
logger.error(f"Error triggering step function: {str(e)}")
# Additional Lambda functions for different purposes
def process_image_upload(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""Process uploaded image"""
try:
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# Download image
s3_object = s3.get_object(Bucket=bucket, Key=key)
image_data = s3_object['Body'].read()
# Process image (resize, compress, etc.)
# This would typically use libraries like Pillow
# Upload processed image
processed_key = f"processed/{key}"
s3.put_object(
Bucket=BUCKET_NAME,
Key=processed_key,
Body=image_data
)
# Update database record
table.update_item(
Key={'id': key.split('/')[-1].split('.')[0]},
UpdateExpression="SET processed_url = :processed_url, processed_at = :processed_at",
ExpressionAttributeValues={
':processed_url': f"s3://{BUCKET_NAME}/{processed_key}",
':processed_at': datetime.utcnow().isoformat()
}
)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Image processed successfully'})
}
except Exception as e:
logger.error(f"Error processing image: {str(e)}")
raise
def cleanup_old_items(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
"""Cleanup old items (scheduled Lambda)"""
try:
# Get items older than 30 days
cutoff_date = datetime.utcnow().replace(day=1) # First day of current month
response = table.scan(
FilterExpression="created_at < :cutoff_date",
ExpressionAttributeValues={':cutoff_date': cutoff_date.isoformat()}
)
old_items = response.get('Items', [])
# Delete old items
for item in old_items:
table.delete_item(Key={'id': item['id']})
# Delete associated files from S3
if 'image_url' in item:
s3_key = item['image_url'].split('/')[-1]
s3.delete_object(Bucket=BUCKET_NAME, Key=s3_key)
return {
'statusCode': 200,
'body': json.dumps({
'message': f'Cleaned up {len(old_items)} old items'
})
}
except Exception as e:
logger.error(f"Error cleaning up old items: {str(e)}")
raise
3. Mejores Prácticas para Dockerfile Cloud Native
# Multi-stage Dockerfile para aplicaciones cloud native
# Stage 1: Etapa de compilación
FROM golang:1.21-alpine AS builder
# Instalar dependencias de compilación
RUN apk add --no-cache git ca-certificates tzdata
# Establecer directorio de trabajo
WORKDIR /app
# Copiar archivos de módulos Go
COPY go.mod go.sum ./
# Descargar dependencias
RUN go mod download
# Copiar código fuente
COPY . .
# Compilar la aplicación
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Stage 2: Etapa de ejecución
FROM alpine:3.18
# Instalar dependencias de ejecución
RUN apk --no-cache add ca-certificates tzdata curl
# Crear usuario no root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Establecer directorio de trabajo
WORKDIR /app
# Copiar binario desde la etapa de compilación
COPY --from=builder /app/main .
# Copiar archivos de configuración
COPY --chown=appuser:appgroup config/ ./config/
# Crear directorios necesarios
RUN mkdir -p /app/logs /app/data && \
chown -R appuser:appgroup /app
# Cambiar a usuario no root
USER appuser
# Verificación de salud
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Exponer puerto
EXPOSE 8080
# Establecer variables de entorno
ENV GIN_MODE=release
ENV PORT=8080
# Comando por defecto
CMD ["./main"]
# Etiquetas de metadatos
LABEL maintainer="devops@company.com" \
version="1.0.0" \
description="Cloud Native Application" \
org.opencontainers.image.source="https://github.com/company/cloud-native-app" \
org.opencontainers.image.licenses="MIT"
# Stage 3: Etapa de desarrollo
FROM builder AS development
# Instalar herramientas de desarrollo
RUN go install github.com/cosmtrek/air@latest
# Configurar entorno de desarrollo
ENV GIN_MODE=debug
# Comando por defecto para desarrollo
CMD ["air", "-c", ".air.toml"]
# Stage 4: Etapa de pruebas
FROM builder AS testing
# Instalar dependencias de pruebas
RUN go install github.com/golang/mock/mockgen@latest && \
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Ejecutar pruebas
RUN go test -v ./... && \
golangci-lint run
# Stage 5: Etapa de análisis de seguridad
FROM builder AS security
# Instalar herramientas de seguridad
RUN go install github.com/securecodewarrior/sdlc-scanner@latest
# Ejecutar análisis de seguridad
RUN sdlc-scanner scan ./...
4. Operador Kubernetes con Go
// main.go
package main
import (
"context"
"flag"
"fmt"
"os"
"time"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
customv1 "github.com/company/cloud-native-operator/api/v1"
"github.com/company/cloud-native-operator/controllers"
//+kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(customv1.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var qps float32
var burst int
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", false,
"If set the metrics endpoint is served securely via HTTPS. "+
"If not set the metrics endpoint is served over HTTP.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.Float32Var(&qps, "kube-api-qps", 50, "QPS to use while talking with the kubernetes api")
flag.IntVar(&burst, "kube-api-burst", 100, "Burst to use while talking with the kubernetes api")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// Configurar el gestor
restConfig := ctrl.GetConfigOrDie()
restConfig.QPS = qps
restConfig.Burst = burst
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "cloud-native-operator.leader.election",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this variable
// to true when the binary is not being immediately ended might cause pending
// requests to be dropped and can lead to inconsistent state.
LeaderElectionReleaseOnCancel: true,
SecureMetrics: secureMetrics,
// NewWebhookServerBuilder configures the webhook server in the manager.
NewWebhookServerBuilder: webhook.NewServer(webhook.Options{
Host: "0.0.0.0",
Port: 9443,
CertDir: "/tmp/k8s-webhook-server/serving-certs",
}),
NewCache: cache.BuilderWithOptions(cache.Options{
ByObject: map[client.Object]cache.ByObject{
&customv1.CloudNativeApp{}: {
Transform: func(ctx context.Context, obj client.Object) (client.Object, error) {
// Transform the object before caching
return obj, nil
},
},
},
}),
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// Configurar servidor de métricas con opciones
if err = mgr.AddMetricsServer(metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
// TODO: Consider using a separate metrics port
// BindAddress: "0.0.0.0:8080",
}); err != nil {
setupLog.Error(err, "unable to set up metrics server")
os.Exit(1)
}
// Añadir verificaciones de salud
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
// Configurar indexadores de campos
if err = setupFieldIndexes(mgr.GetCache()); err != nil {
setupLog.Error(err, "unable to set up field indexes")
os.Exit(1)
}
// Configurar controladores
if err = setupControllers(mgr); err != nil {
setupLog.Error(err, "unable to set up controllers")
os.Exit(1)
}
// Configurar webhooks
if err = setupWebhooks(mgr); err != nil {
setupLog.Error(err, "unable to set up webhooks")
os.Exit(1)
}
// Iniciar el gestor
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
func setupFieldIndexes(cache cache.Cache) error {
// Configurar indexadores de campos personalizados
if err := cache.IndexField(context.Background(), &customv1.CloudNativeApp{}, ".spec.status", func(obj client.Object) []string {
app := obj.(*customv1.CloudNativeApp)
return []string{string(app.Spec.Status)}
}); err != nil {
return fmt.Errorf("failed to set up index for CloudNativeApp.spec.status: %w", err)
}
return nil
}
func setupControllers(mgr ctrl.Manager) error {
// Configurar controlador CloudNativeApp
if err := (&controllers.CloudNativeAppReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
EventRecorder: mgr.GetEventRecorderFor("cloud-native-app-controller"),
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to create CloudNativeApp controller: %w", err)
}
// Configurar controladores adicionales aquí
//+kubebuilder:scaffold:builder
return nil
}
func setupWebhooks(mgr ctrl.Manager) error {
// Configurar webhook CloudNativeApp
if err := (&customv1.CloudNativeApp{}).SetupWebhookWithManager(mgr); err != nil {
return fmt.Errorf("unable to set up CloudNativeApp webhook: %w", err)
}
return nil
}
// controllers/cloudnativeapp_controller.go
package controllers
import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
customv1 "github.com/company/cloud-native-operator/api/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CloudNativeAppReconciler reconciles a CloudNativeApp object
type CloudNativeAppReconciler struct {
client.Client
Scheme *runtime.Scheme
EventRecorder record.EventRecorder
}
//+kubebuilder:rbac:groups=custom.company.com,resources=cloudnativeapps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=custom.company.com,resources=cloudnativeapps/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=custom.company.com,resources=cloudnativeapps/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
// Reconcile es parte del bucle de reconciliación principal de Kubernetes, que intenta
// acercar el estado actual del clúster al estado deseado.
func (r *CloudNativeAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Obtener la instancia de CloudNativeApp
var cloudNativeApp customv1.CloudNativeApp
if err := r.Get(ctx, req.NamespacedName, &cloudNativeApp); err != nil {
if errors.IsNotFound(err) {
// Objeto no encontrado, retornar. Los objetos creados se limpian automáticamente.
return ctrl.Result{}, nil
}
logger.Error(err, "unable to fetch CloudNativeApp")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Verificar si el deployment existe
deployment := &appsv1.Deployment{}
deploymentName := fmt.Sprintf("%s-deployment", cloudNativeApp.Name)
err := r.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: cloudNativeApp.Namespace}, deployment)
if err != nil && errors.IsNotFound(err) {
// Crear deployment
deployment, err = r.createDeployment(ctx, &cloudNativeApp)
if err != nil {
logger.Error(err, "unable to create deployment")
return ctrl.Result{}, err
}
r.EventRecorder.Event(&cloudNativeApp, corev1.EventTypeNormal, "Created", "Deployment created")
} else if err != nil {
logger.Error(err, "unable to get deployment")
return ctrl.Result{}, err
} else {
// Actualizar deployment
if err := r.updateDeployment(ctx, &cloudNativeApp, deployment); err != nil {
logger.Error(err, "unable to update deployment")
return ctrl.Result{}, err
}
}
// Verificar si el servicio existe
service := &corev1.Service{}
serviceName := fmt.Sprintf("%s-service", cloudNativeApp.Name)
err = r.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: cloudNativeApp.Namespace}, service)
if err != nil && errors.IsNotFound(err) {
// Crear servicio
service, err = r.createService(ctx, &cloudNativeApp)
if err != nil {
logger.Error(err, "unable to create service")
return ctrl.Result{}, err
}
r.EventRecorder.Event(&cloudNativeApp, corev1.EventTypeNormal, "Created", "Service created")
} else if err != nil {
logger.Error(err, "unable to get service")
return ctrl.Result{}, err
} else {
// Actualizar servicio
if err := r.updateService(ctx, &cloudNativeApp, service); err != nil {
logger.Error(err, "unable to update service")
return ctrl.Result{}, err
}
}
// Actualizar estado
if err := r.updateStatus(ctx, &cloudNativeApp, deployment); err != nil {
logger.Error(err, "unable to update status")
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: time.Minute * 5}, nil
}
func (r *CloudNativeAppReconciler) createDeployment(ctx context.Context, app *customv1.CloudNativeApp) (*appsv1.Deployment, error) {
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-deployment", app.Name),
Namespace: app.Namespace,
Labels: map[string]string{
"app": app.Name,
"managed": "cloud-native-operator",
},
},
Spec: appsv1.DeploymentSpec{
Replicas: app.Spec.Replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": app.Name,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": app.Name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: app.Name,
Image: app.Spec.Image,
Ports: []corev1.ContainerPort{
{
ContainerPort: app.Spec.Port,
},
},
Env: app.Spec.Env,
Resources: app.Spec.Resources,
LivenessProbe: app.Spec.LivenessProbe,
ReadinessProbe: app.Spec.ReadinessProbe,
},
},
},
},
},
}
// Establecer referencia del controlador
if err := controllerutil.SetControllerReference(app, deployment, r.Scheme); err != nil {
return nil, err
}
if err := r.Create(ctx, deployment); err != nil {
return nil, err
}
return deployment, nil
}
func (r *CloudNativeAppReconciler) updateDeployment(ctx context.Context, app *customv1.CloudNativeApp, deployment *appsv1.Deployment) error {
// Actualizar especificación del deployment
deployment.Spec.Replicas = app.Spec.Replicas
deployment.Spec.Template.Spec.Containers[0].Image = app.Spec.Image
deployment.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort = app.Spec.Port
deployment.Spec.Template.Spec.Containers[0].Env = app.Spec.Env
deployment.Spec.Template.Spec.Containers[0].Resources = app.Spec.Resources
deployment.Spec.Template.Spec.Containers[0].LivenessProbe = app.Spec.LivenessProbe
deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = app.Spec.ReadinessProbe
return r.Update(ctx, deployment)
}
func (r *CloudNativeAppReconciler) createService(ctx context.Context, app *customv1.CloudNativeApp) (*corev1.Service, error) {
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-service", app.Name),
Namespace: app.Namespace,
Labels: map[string]string{
"app": app.Name,
"managed": "cloud-native-operator",
},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": app.Name,
},
Ports: []corev1.ServicePort{
{
Port: app.Spec.ServicePort,
TargetPort: app.Spec.Port,
Protocol: corev1.ProtocolTCP,
},
},
Type: corev1.ServiceTypeClusterIP,
},
}
// Establecer referencia del controlador
if err := controllerutil.SetControllerReference(app, service, r.Scheme); err != nil {
return nil, err
}
if err := r.Create(ctx, service); err != nil {
return nil, err
}
return service, nil
}
func (r *CloudNativeAppReconciler) updateService(ctx context.Context, app *customv1.CloudNativeApp, service *corev1.Service) error {
// Actualizar especificación del servicio
service.Spec.Ports[0].Port = app.Spec.ServicePort
service.Spec.Ports[0].TargetPort = intstr.FromInt(app.Spec.Port)
return r.Update(ctx, service)
}
func (r *CloudNativeAppReconciler) updateStatus(ctx context.Context, app *customv1.CloudNativeApp, deployment *appsv1.Deployment) error {
app.Status.Replicas = deployment.Status.Replicas
app.Status.ReadyReplicas = deployment.Status.ReadyReplicas
app.Status.UpdatedReplicas = deployment.Status.UpdatedReplicas
app.Status.AvailableReplicas = deployment.Status.AvailableReplicas
// Establecer condiciones
conditions := []customv1.CloudNativeAppCondition{
{
Type: customv1.ConditionReady,
Status: corev1.ConditionTrue,
},
}
app.Status.Conditions = conditions
return r.Status().Update(ctx, app)
}
// SetupWithManager sets up the controller with the Manager.
func (r *CloudNativeAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&customv1.CloudNativeApp{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Complete(r)
}
Arquitectura Cloud Native
Stack Cloud Native
graph TD
A[Developer] --> B[CI/CD Pipeline]
B --> C[Container Registry]
C --> D[Kubernetes Cluster]
D --> E[Pods & Containers]
D --> F[Services & Ingress]
D --> G[ConfigMaps & Secrets]
E --> H[Application Code]
F --> I[Load Balancer]
G --> J[Configuration]
K[Service Mesh] --> E
L[Monitoring] --> D
M[Logging] --> D
N[Security] --> D
O[Serverless] --> P[Functions]
P --> Q[Event Sources]
R[GitOps] --> D
S[IaC Tools] --> D
Containerización vs. Serverless
Tabla comparativa
| Aspecto | Containers | Serverless |
|---|---|---|
| Control | Control total | Infraestructura gestionada |
| Costos | Costos continuos | Pago por uso |
| Escalado | Manual/Auto-scaling | Automático |
| Cold Starts | Ninguno | Sí |
| Gestión de estado | Persistente | Efímero |
| Portabilidad | Alta | Específica de la nube |
Patrones Cloud Native
Patrón Sidecar
# Sidecar container example
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: main-app
image: myapp:1.0
ports:
- containerPort: 8080
- name: logging-sidecar
image: logging-sidecar:1.0
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
volumes:
- name: shared-logs
emptyDir: {}
Patrón Ambassador
# API Gateway as ambassador
apiVersion: v1
kind: Service
metadata:
name: api-gateway
spec:
selector:
app: api-gateway
ports:
- port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
spec:
replicas: 2
selector:
matchLabels:
app: api-gateway
template:
metadata:
labels:
app: api-gateway
spec:
containers:
- name: gateway
image: api-gateway:1.0
ports:
- containerPort: 8080
env:
- name: BACKEND_URL
value: "http://backend-service:8080"
Ventajas e inconvenientes
Ventajas de Cloud Native
- Escalabilidad: Escalado horizontal automático
- Resiliencia: Sistemas autorreparables
- Portabilidad: Aplicaciones independientes de la nube
- Eficiencia: Optimización de recursos
- Rapidez: Ciclos de despliegue acelerados
Inconvenientes
- Complejidad: Alta complejidad técnica
- Curva de aprendizaje: Empinada para los equipos
- Costos: Gastos impredecibles en Serverless
- Vendor Lock-in: Características específicas de la nube
- Debugging: Depuración compleja en sistemas distribuidos
Preguntas frecuentes en exámenes
-
¿Cuál es la diferencia entre Containers y Serverless? Los Containers ofrecen control total sobre el runtime, mientras que Serverless gestiona completamente la infraestructura y cobra por uso.
-
¡Explica el patrón Sidecar! El patrón Sidecar añade contenedores adicionales a un contenedor principal para proporcionar funcionalidades de apoyo como Logging, Monitoring o Security.
-
¿Cuándo usas Kubernetes vs. Serverless? Usa Kubernetes para aplicaciones complejas y con estado que requieran control total, y Serverless para funciones dirigidas por eventos, sin estado y baja complejidad.
-
¿Qué hace que una aplicación sea “Cloud Native”? Las aplicaciones Cloud Native utilizan Containers, Microservices, orquestación dinámica y entrega continua para maximizar el aprovechamiento de la nube.



