# Kubernetes: Desplegando tu primera aplicación > Guía práctica para desplegar una aplicación en Kubernetes. Desde la instalación de minikube hasta Deployments, Services y ConfigMaps con ejemplos concretos. - Published: 2026-02-12 - Updated: 2026-04-28 - Author: SharpSkill - Tags: kubernetes, k8s, container orchestration, devops, deployment - Reading time: 16 min --- Kubernetes (K8s) se ha convertido en el estándar de facto para la orquestación de contenedores. Diseñado por Google y mantenido actualmente por la CNCF, Kubernetes automatiza el despliegue, el escalado y la gestión de aplicaciones contenedorizadas. Esta guía recorre la configuración de un clúster local y el despliegue de una primera aplicación. > **Requisitos previos** > > Se recomienda tener conocimientos básicos de Docker antes de profundizar en Kubernetes. Los contenedores son los bloques fundamentales que Kubernetes orquesta. Leer la guía de Docker antes facilita mucho la comprensión de los conceptos presentados aquí. ## Comprender la arquitectura de Kubernetes Kubernetes se basa en una arquitectura maestro-trabajador. El Control Plane toma las decisiones globales sobre el clúster, mientras que los Nodes ejecutan las cargas de trabajo. ``` # Simplified Kubernetes Architecture ┌─────────────────────────────────────────────────────────────┐ │ CONTROL PLANE │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ API Server │ │ Scheduler │ │ Controller Manager │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ etcd ││ │ └─────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────┘ │ ┌────────────────────┼────────────────────┐ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ NODE 1 │ │ NODE 2 │ │ NODE 3 │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │ kubelet │ │ │ │ kubelet │ │ │ │ kubelet │ │ │ ├───────────┤ │ │ ├───────────┤ │ │ ├───────────┤ │ │ │ kube-proxy│ │ │ │ kube-proxy│ │ │ │ kube-proxy│ │ │ ├───────────┤ │ │ ├───────────┤ │ │ ├───────────┤ │ │ │ Pods │ │ │ │ Pods │ │ │ │ Pods │ │ │ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` El API Server es el punto de entrada para todos los comandos. etcd almacena el estado del clúster. El Scheduler asigna los Pods a los Nodes. Los Controllers mantienen el estado deseado del sistema. ## Configurar el entorno local Existen varias opciones para experimentar con Kubernetes localmente: minikube, kind, k3d o Docker Desktop. Minikube sigue siendo la solución más popular para aprender. ```bash # terminal # Install kubectl (Kubernetes client) curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/ # Verify installation kubectl version --client # Client Version: v1.31.0 # Install minikube curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube # Start the local cluster minikube start --driver=docker --cpus=2 --memory=4096 # Check status minikube status # minikube: Running # cluster: Running # kubectl: Configured ``` Minikube crea un clúster Kubernetes de un solo nodo dentro de una máquina virtual o un contenedor Docker. Los recursos asignados (CPU, memoria) se ajustan según las necesidades. ```bash # terminal # Access the Kubernetes dashboard (web interface) minikube dashboard # Check cluster nodes kubectl get nodes # NAME STATUS ROLES AGE VERSION # minikube Ready control-plane 5m v1.31.0 # Detailed cluster information kubectl cluster-info ``` > **Alternativas a Minikube** > > Kind (Kubernetes in Docker) arranca más rápido y se adapta mejor a las pruebas en CI/CD. K3d utiliza k3s, una distribución ligera de Kubernetes. Docker Desktop integra Kubernetes directamente, pero consume más recursos. ## Pods: la unidad básica Un Pod es la unidad desplegable más pequeña en Kubernetes. Un Pod encapsula uno o varios contenedores que comparten la misma red y el mismo almacenamiento. ```yaml # pod-simple.yaml apiVersion: v1 kind: Pod metadata: # Unique Pod name within the namespace name: nginx-pod # Labels for organization and selection labels: app: nginx environment: development spec: containers: # Main container definition - name: nginx # Docker image to use image: nginx:1.25-alpine # Ports exposed by the container ports: - containerPort: 80 # Container resource allocation resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m" ``` Este manifiesto YAML declara un Pod que contiene un solo contenedor nginx. Las labels permiten identificar y seleccionar los Pods. Los recursos definen las garantías mínimas (requests) y los límites máximos (limits). ```bash # terminal # Create the Pod kubectl apply -f pod-simple.yaml # pod/nginx-pod created # List Pods kubectl get pods # NAME READY STATUS RESTARTS AGE # nginx-pod 1/1 Running 0 30s # Full Pod details kubectl describe pod nginx-pod # Container logs kubectl logs nginx-pod # Execute a command inside the Pod kubectl exec -it nginx-pod -- /bin/sh # Delete the Pod kubectl delete pod nginx-pod ``` Los Pods son efímeros por naturaleza. En caso de caída o eliminación, Kubernetes no los recrea automáticamente. Los Deployments resuelven esta limitación. ## Deployments: gestión declarativa Un Deployment define el estado deseado para un conjunto de Pods idénticos. Kubernetes mantiene automáticamente este estado creando, actualizando o eliminando Pods según sea necesario. ```yaml # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: # Deployment name name: webapp-deployment labels: app: webapp spec: # Desired number of replicas replicas: 3 # Selector to identify managed Pods selector: matchLabels: app: webapp # Template for Pod creation template: metadata: labels: app: webapp spec: containers: - name: webapp image: nginx:1.25-alpine ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m" # Liveness probe: restarts container on failure livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 10 # Readiness probe: removes Pod from Service on failure readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 ``` El Deployment crea un ReplicaSet que mantiene 3 Pods idénticos. Las probes verifican el estado de los contenedores y permiten a Kubernetes reaccionar automáticamente ante incidencias. ```bash # terminal # Create the Deployment kubectl apply -f deployment.yaml # deployment.apps/webapp-deployment created # Verify the Deployment kubectl get deployments # NAME READY UP-TO-DATE AVAILABLE AGE # webapp-deployment 3/3 3 3 1m # List Pods created by the Deployment kubectl get pods -l app=webapp # NAME READY STATUS RESTARTS AGE # webapp-deployment-7d9f8b6c4-abc12 1/1 Running 0 1m # webapp-deployment-7d9f8b6c4-def34 1/1 Running 0 1m # webapp-deployment-7d9f8b6c4-ghi56 1/1 Running 0 1m # Manual scaling kubectl scale deployment webapp-deployment --replicas=5 # Deployment history kubectl rollout history deployment webapp-deployment ``` Eliminar un Pod activa automáticamente la creación de un nuevo Pod para mantener el número deseado de réplicas. ## Services: exposición de red Los Pods tienen direcciones IP efímeras. Los Services proporcionan una dirección estable para acceder a un conjunto de Pods, con balanceo de carga integrado. ```yaml # service.yaml apiVersion: v1 kind: Service metadata: name: webapp-service spec: # Service type: ClusterIP (internal), NodePort, LoadBalancer type: ClusterIP # Selector to identify target Pods selector: app: webapp ports: # Port exposed by the Service - port: 80 # Target container port targetPort: 80 # Protocol (TCP by default) protocol: TCP ``` Este Service ClusterIP solo es accesible desde dentro del clúster. Las solicitudes a `webapp-service:80` se distribuyen entre los Pods que llevan la label `app: webapp`. ```bash # terminal # Create the Service kubectl apply -f service.yaml # service/webapp-service created # List Services kubectl get services # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # webapp-service ClusterIP 10.96.123.456 80/TCP 30s # Test from a temporary Pod kubectl run curl-test --rm -it --image=curlimages/curl -- curl webapp-service # Detailed Service description kubectl describe service webapp-service ``` Para exponer la aplicación fuera del clúster, es necesario un tipo NodePort o LoadBalancer. ```yaml # service-nodeport.yaml apiVersion: v1 kind: Service metadata: name: webapp-nodeport spec: type: NodePort selector: app: webapp ports: - port: 80 targetPort: 80 # Port on each Node (30000-32767) nodePort: 30080 ``` Con minikube, el comando `minikube service webapp-nodeport` abre automáticamente el navegador en la URL correcta. ## ConfigMaps: configuración externalizada Los ConfigMaps separan la configuración del código. Los valores se inyectan como variables de entorno o se montan como archivos. ```yaml # configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: webapp-config data: # Simple key-value pairs APP_ENV: "production" LOG_LEVEL: "info" MAX_CONNECTIONS: "100" # Multiline configuration (complete file) nginx.conf: | server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } location /health { return 200 'OK'; add_header Content-Type text/plain; } } ``` Los ConfigMaps almacenan datos no sensibles. Para los secretos (contraseñas, tokens), los Secrets de Kubernetes son más apropiados. ```yaml # deployment-with-config.yaml apiVersion: apps/v1 kind: Deployment metadata: name: webapp-configured spec: replicas: 2 selector: matchLabels: app: webapp-configured template: metadata: labels: app: webapp-configured spec: containers: - name: webapp image: nginx:1.25-alpine ports: - containerPort: 80 # Inject environment variables envFrom: - configMapRef: name: webapp-config # Or individual variables env: - name: SPECIFIC_VAR valueFrom: configMapKeyRef: name: webapp-config key: LOG_LEVEL # Mount configuration file volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: nginx.conf volumes: - name: nginx-config configMap: name: webapp-config ``` Esta configuración inyecta variables de entorno y monta el archivo nginx.conf dentro del contenedor. ```bash # terminal # Apply resources kubectl apply -f configmap.yaml kubectl apply -f deployment-with-config.yaml # Verify environment variables kubectl exec deployment/webapp-configured -- printenv | grep APP_ENV # APP_ENV=production # Verify mounted file kubectl exec deployment/webapp-configured -- cat /etc/nginx/conf.d/default.conf ``` > **Actualización de ConfigMaps** > > Modificar un ConfigMap no reinicia automáticamente los Pods. Para aplicar los cambios, hace falta un reinicio manual: `kubectl rollout restart deployment webapp-configured`. Herramientas como Reloader automatizan este proceso. ## Secrets: datos sensibles Los Secrets almacenan información sensible como contraseñas, tokens o claves SSH. Aunque están codificados en base64, no se cifran por defecto en reposo. ```yaml # secret.yaml apiVersion: v1 kind: Secret metadata: name: webapp-secrets type: Opaque # Values must be base64-encoded data: # echo -n 'admin' | base64 username: YWRtaW4= # echo -n 'supersecretpassword' | base64 password: c3VwZXJzZWNyZXRwYXNzd29yZA== --- # Alternative: stringData accepts plain text values apiVersion: v1 kind: Secret metadata: name: webapp-secrets-plain type: Opaque stringData: username: admin password: supersecretpassword ``` Los Secrets se inyectan de la misma forma que los ConfigMaps. ```yaml # deployment-with-secrets.yaml apiVersion: apps/v1 kind: Deployment metadata: name: webapp-secure spec: replicas: 1 selector: matchLabels: app: webapp-secure template: metadata: labels: app: webapp-secure spec: containers: - name: webapp image: nginx:1.25-alpine env: - name: DB_USERNAME valueFrom: secretKeyRef: name: webapp-secrets key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: webapp-secrets key: password ``` ```bash # terminal # Create the Secret kubectl apply -f secret.yaml # List Secrets (values are not displayed) kubectl get secrets # NAME TYPE DATA AGE # webapp-secrets Opaque 2 10s # Decode a value kubectl get secret webapp-secrets -o jsonpath='{.data.password}' | base64 -d # supersecretpassword ``` ## Namespaces: aislamiento lógico Los Namespaces dividen un clúster en entornos virtuales aislados. Esta separación permite gestionar varios equipos o entornos sobre el mismo clúster. ```yaml # namespace.yaml apiVersion: v1 kind: Namespace metadata: name: development labels: environment: development --- apiVersion: v1 kind: Namespace metadata: name: staging labels: environment: staging ``` Cada recurso puede crearse dentro de un namespace específico. ```bash # terminal # Create namespaces kubectl apply -f namespace.yaml # List namespaces kubectl get namespaces # NAME STATUS AGE # default Active 1d # development Active 10s # staging Active 10s # Create a resource in a specific namespace kubectl apply -f deployment.yaml -n development # List Pods in a namespace kubectl get pods -n development # Change default namespace kubectl config set-context --current --namespace=development ``` Los recursos de namespaces distintos están aislados por defecto. La comunicación entre namespaces se realiza mediante el DNS interno: `service-name.namespace.svc.cluster.local`. ## Aplicación completa: ensamblar los recursos A continuación se presenta una aplicación completa que combina todos los conceptos vistos. ```yaml # complete-app.yaml --- # Dedicated Namespace apiVersion: v1 kind: Namespace metadata: name: myapp --- # ConfigMap for configuration apiVersion: v1 kind: ConfigMap metadata: name: myapp-config namespace: myapp data: APP_NAME: "MyApp" LOG_LEVEL: "info" --- # Secret for sensitive data apiVersion: v1 kind: Secret metadata: name: myapp-secrets namespace: myapp type: Opaque stringData: api-key: "sk-1234567890abcdef" --- # Deployment with 3 replicas apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: nginx:1.25-alpine ports: - containerPort: 80 envFrom: - configMapRef: name: myapp-config env: - name: API_KEY valueFrom: secretKeyRef: name: myapp-secrets key: api-key resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m" livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 --- # Service for internal exposure apiVersion: v1 kind: Service metadata: name: myapp-service namespace: myapp spec: type: ClusterIP selector: app: myapp ports: - port: 80 targetPort: 80 --- # NodePort Service for external access (development) apiVersion: v1 kind: Service metadata: name: myapp-nodeport namespace: myapp spec: type: NodePort selector: app: myapp ports: - port: 80 targetPort: 80 nodePort: 30100 ``` Este único archivo despliega una aplicación completa con configuración externalizada, secretos, alta disponibilidad y exposición de red. ```bash # terminal # Deploy the complete application kubectl apply -f complete-app.yaml # Verify all resources kubectl get all -n myapp # NAME READY STATUS RESTARTS AGE # pod/myapp-7d9f8b6c4-abc12 1/1 Running 0 30s # pod/myapp-7d9f8b6c4-def34 1/1 Running 0 30s # pod/myapp-7d9f8b6c4-ghi56 1/1 Running 0 30s # # NAME TYPE CLUSTER-IP PORT(S) AGE # service/myapp-service ClusterIP 10.96.123.456 80/TCP 30s # service/myapp-nodeport NodePort 10.96.123.789 80:30100/TCP 30s # # NAME READY UP-TO-DATE AVAILABLE AGE # deployment.apps/myapp 3/3 3 3 30s # Access the application with minikube minikube service myapp-nodeport -n myapp ``` ## Actualizaciones y rollbacks Kubernetes facilita las actualizaciones progresivas y los rollbacks. ```bash # terminal # Update the Deployment image kubectl set image deployment/myapp myapp=nginx:1.26-alpine -n myapp # Track deployment in real-time kubectl rollout status deployment/myapp -n myapp # Waiting for deployment "myapp" rollout to finish: 1 out of 3 new replicas updated # Waiting for deployment "myapp" rollout to finish: 2 out of 3 new replicas updated # deployment "myapp" successfully rolled out # Revision history kubectl rollout history deployment/myapp -n myapp # REVISION CHANGE-CAUSE # 1 # 2 # Rollback to previous revision kubectl rollout undo deployment/myapp -n myapp # Rollback to a specific revision kubectl rollout undo deployment/myapp --to-revision=1 -n myapp ``` La estrategia de actualización por defecto (RollingUpdate) reemplaza progresivamente los Pods antiguos por los nuevos, garantizando la disponibilidad continua. ## Comandos kubectl esenciales ```bash # terminal # ======================================== # General Information # ======================================== kubectl cluster-info # Cluster info kubectl get nodes -o wide # Nodes with details kubectl api-resources # List resource types # ======================================== # Resource Management # ======================================== kubectl get all # All namespace resources kubectl get pods -A # Pods from all namespaces kubectl get pods -o wide # Pods with IP and Node kubectl get pods -w # Watch mode (real-time) # ======================================== # Inspection and Debugging # ======================================== kubectl describe pod # Full details kubectl logs -f # Streaming logs kubectl logs -c # Specific container logs kubectl exec -it -- /bin/sh # Interactive shell kubectl port-forward 8080:80 # Local tunnel to Pod # ======================================== # Editing and Deletion # ======================================== kubectl edit deployment # Live editing (vi) kubectl delete -f manifest.yaml # Delete via file kubectl delete pod --force # Force deletion ``` ## Conclusión Kubernetes transforma la gestión de aplicaciones contenedorizadas al ofrecer un marco declarativo, resiliente y extensible. Los conceptos fundamentales presentados aquí son la base para los despliegues listos para producción. ### Checklist para un primer despliegue Kubernetes - ✅ Clúster local funcional (minikube, kind o k3d) - ✅ kubectl instalado y configurado - ✅ Deployment con réplicas y health probes - ✅ Service para la exposición de red - ✅ ConfigMap para la configuración externalizada - ✅ Secret para los datos sensibles - ✅ Namespace para el aislamiento - ✅ Límites de recursos definidos (requests/limits) - ✅ Estrategia de actualización y rollback dominados Dominar Kubernetes abre la puerta a arquitecturas cloud-native escalables. Los siguientes pasos incluyen explorar los Ingress Controllers para el enrutamiento HTTP, los PersistentVolumes para el almacenamiento y Helm para la gestión de paquetes. Kubernetes se convierte en un activo importante durante las entrevistas DevOps y SRE. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/devops/kubernetes-deploying-first-application