How to Deploy an Application to Kubernetes on RHEL 7
Deploying applications to Kubernetes involves more than simply running a container — it means defining how many replicas should run, how the application is exposed to network traffic, how it recovers from failure, and how updates are rolled out without downtime. Kubernetes provides two core objects for this: the Deployment, which manages the desired state of your application Pods, and the Service, which provides a stable network endpoint to reach them. This tutorial covers both imperative (kubectl create) and declarative (YAML manifest) approaches for deploying an application on a RHEL 7 Kubernetes cluster, along with monitoring rollout status, troubleshooting Pods, and understanding the different Service types available.
Prerequisites
- A running Kubernetes cluster (kubeadm or k3s) with at least one worker node
kubectlconfigured and pointing at your cluster- Basic familiarity with YAML syntax
- The application container image available in a registry (Docker Hub used in examples)
Step 1: Create a Deployment Imperatively with kubectl
The fastest way to get an application running is to use kubectl create deployment. This is ideal for testing and learning, though the declarative YAML approach is preferred for production.
# Deploy an nginx container with 3 replicas
kubectl create deployment my-nginx
--image=nginx:1.25
--replicas=3
# Verify the deployment was created
kubectl get deployments
# Expected output
NAME READY UP-TO-DATE AVAILABLE AGE
my-nginx 3/3 3 3 30s
# List the pods created by the deployment
kubectl get pods -l app=my-nginx
Step 2: Expose the Deployment as a Service with kubectl expose
A Deployment alone does not expose your application to network traffic. Use kubectl expose to create a Service that routes traffic to the Deployment’s Pods.
# Create a ClusterIP service (internal only)
kubectl expose deployment my-nginx
--type=ClusterIP
--port=80
--name=my-nginx-svc
# Create a NodePort service (accessible from outside the cluster)
kubectl expose deployment my-nginx
--type=NodePort
--port=80
--name=my-nginx-nodeport
# List services
kubectl get svc
# Example output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-svc ClusterIP 10.96.45.12 <none> 80/TCP 20s
my-nginx-nodeport NodePort 10.96.88.34 <none> 80:31456/TCP 5s
Step 3: Write a Deployment Manifest (Declarative YAML)
For production use, always manage Kubernetes resources with YAML manifests committed to version control. The following manifest creates a Deployment with resource limits and a liveness probe.
cat > deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: default
labels:
app: my-nginx
version: "1.25"
spec:
replicas: 3
selector:
matchLabels:
app: my-nginx
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-nginx
version: "1.25"
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
protocol: TCP
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "250m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
EOF
Step 4: Write Service Manifests (ClusterIP, NodePort, LoadBalancer)
A Service YAML file defines how the application is exposed. The three most common type values serve different networking scenarios.
cat > service.yaml <<'EOF'
# --- ClusterIP: reachable only within the cluster ---
apiVersion: v1
kind: Service
metadata:
name: my-nginx-clusterip
spec:
type: ClusterIP
selector:
app: my-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
# --- NodePort: reachable from outside using <NodeIP>:<NodePort> ---
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
spec:
type: NodePort
selector:
app: my-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080 # Must be in range 30000-32767
---
# --- LoadBalancer: requests an external LB from the cloud provider ---
apiVersion: v1
kind: Service
metadata:
name: my-nginx-lb
spec:
type: LoadBalancer
selector:
app: my-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
Step 5: Apply Manifests with kubectl apply
The kubectl apply command is idempotent — it creates the resource if it does not exist, or updates it if it does. Always prefer apply over create for declarative workflows.
# Apply the deployment manifest
kubectl apply -f deployment.yaml
# Apply the service manifest
kubectl apply -f service.yaml
# Apply an entire directory of manifests
kubectl apply -f ./manifests/
# Perform a dry run to preview changes without applying
kubectl apply -f deployment.yaml --dry-run=client
Step 6: Monitor Rollout Status
After applying a Deployment, use kubectl rollout status to watch the rolling update complete. This is especially important during upgrades.
# Watch rollout progress
kubectl rollout status deployment/my-nginx
# Expected output when successful
Waiting for deployment "my-nginx" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "my-nginx" rollout to finish: 2 of 3 updated replicas are available...
deployment "my-nginx" successfully rolled out
# View rollout history
kubectl rollout history deployment/my-nginx
# Roll back to the previous version if needed
kubectl rollout undo deployment/my-nginx
Step 7: Scale the Deployment
# Scale to 5 replicas imperatively
kubectl scale deployment my-nginx --replicas=5
# Or edit the YAML and re-apply
# Change spec.replicas to 5 in deployment.yaml then:
kubectl apply -f deployment.yaml
# Verify scaling
kubectl get deployment my-nginx
kubectl get pods -l app=my-nginx
Step 8: Inspect and Troubleshoot Pods with kubectl describe
When a Pod is not starting or behaving unexpectedly, kubectl describe pod provides detailed event logs and status information.
# List all pods with status
kubectl get pods -l app=my-nginx -o wide
# Describe a specific pod (replace pod name with actual)
kubectl describe pod my-nginx-6d8f4b9b7c-xk2pq
# View pod logs
kubectl logs my-nginx-6d8f4b9b7c-xk2pq
# Follow logs in real time
kubectl logs -f my-nginx-6d8f4b9b7c-xk2pq
# View logs from a previous crashed container
kubectl logs my-nginx-6d8f4b9b7c-xk2pq --previous
# Execute a shell inside a running pod for debugging
kubectl exec -it my-nginx-6d8f4b9b7c-xk2pq -- /bin/bash
Step 9: Update the Application Image
To deploy a new version of your application, update the container image in the Deployment. Kubernetes performs a rolling update, replacing Pods one at a time with zero downtime.
# Update image imperatively
kubectl set image deployment/my-nginx nginx=nginx:1.26
# Or update deployment.yaml and re-apply:
# Change image: nginx:1.25 to image: nginx:1.26 then:
kubectl apply -f deployment.yaml
# Monitor the rolling update
kubectl rollout status deployment/my-nginx
Step 10: Clean Up Resources
# Delete the deployment
kubectl delete deployment my-nginx
# Delete services
kubectl delete svc my-nginx-clusterip my-nginx-nodeport my-nginx-lb
# Delete using the manifest files (removes all resources defined in them)
kubectl delete -f deployment.yaml
kubectl delete -f service.yaml
You now have a solid foundation for deploying and managing applications in Kubernetes on RHEL 7. The combination of Deployment manifests with replica management, rolling update strategies, resource limits, and health probes ensures your application runs reliably and recovers automatically from failures. Service objects — ClusterIP for internal communication, NodePort for direct external access, and LoadBalancer for cloud environments — give you flexible control over network exposure. As your application complexity grows, explore Ingress controllers for HTTP routing, ConfigMaps and Secrets for externalized configuration, and Horizontal Pod Autoscalers for automatic scaling based on CPU and memory metrics.