Kubernetes / Runbook
Kubernetes runbook and tutorials
Step-by-step production runbook for deploying multi-tier workloads, managing clusters with kubectl, configuring VPC gateway port forwarding, and accessing the Kubernetes Dashboard.
Overview
Pantech Cloud provides sovereign, managed Kubernetes clusters where both control plane and worker nodes run as dedicated virtual machines in your private VPC network.
This runbook walks you through the entire lifecycle:
- Creating a cluster and setting up your local
kubectlenvironment. - Deploying real-world, multi-tier application blueprints (including stateful databases, Redis caches, REST APIs, and modern web frontends).
- Exposing containerized services publicly using VPC gateway port forwarding and NodePort services.
- Accessing the official Kubernetes Dashboard Web UI for live workload inspection and real-time pod telemetry.
Step 1: Create a cluster
Before deploying workloads, create a managed Kubernetes cluster in your target VPC:
- In the console, navigate to Kubernetes in the left sidebar and click Create cluster.
- Zone: Select your deployment zone (for example,
af-abj-2). - VPC and Subnet: Choose the private VPC network and subnet where your nodes will reside (for example,
k8s-subnetinprod-vpc). - Kubernetes Version: Choose the latest stable release (such as
1.31.1). - Node Plan: Choose the VM specification for your nodes (for example,
std-2c-4gfor 2 vCPUs and 4 GB RAM). - Worker Nodes: Choose the desired worker count (for example, 2 nodes).
- Cluster Name: Provide a unique, lowercase name (for example,
production-apps). - Review the configuration and click Create cluster.
The cluster provisions automatically in several minutes. Once the cluster phase displays Running, you can download your cluster credentials.
Step 2: Connect with kubectl
The kubeconfig contains the cluster-admin credential required to authenticate and interact with your cluster's API server.
1. Download and secure your kubeconfig
On your cluster's detail page, click Download kubeconfig in the top action bar (or in the Cluster details section). Save the YAML file to your local computer:
$ chmod 600 ~/Downloads/production-apps-kubeconfig.yaml$ export KUBECONFIG=~/Downloads/production-apps-kubeconfig.yaml2. Verify cluster nodes
Test connectivity to the Kubernetes API server and verify all nodes are Ready:
$ kubectl get nodes -o wideNAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGEproduction-apps-control-1 Ready control-plane 10m v1.31.1 10.0.1.201 Ubuntu 24.04 LTSproduction-apps-worker-1 Ready worker 8m v1.31.1 10.0.1.202 Ubuntu 24.04 LTSproduction-apps-worker-2 Ready worker 8m v1.31.1 10.0.1.203 Ubuntu 24.04 LTSStep 3: Application Blueprints
Below are complete, production-ready manifests for multi-tier applications tested on Pantech Kubernetes.
Blueprint A: Full-Stack Social Media Platform
This architecture demonstrates:
- Persistent Storage: PostgreSQL 16 database running as a
StatefulSetwith an automatedPersistentVolumeClaim. - In-Memory Cache: Redis 7.2 for sub-millisecond session state and timeline caching.
- Backend API: Node.js/TypeScript REST API exposed on NodePort
30085. - Frontend UI: Next.js 15 responsive web interface exposed on NodePort
30084.
Save the following manifest as social-media-app.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: social-media
---
# PostgreSQL Database (StatefulSet with Persistent Volume Claim)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: social-media
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_DB
value: social_db
- name: POSTGRES_USER
value: pantech_user
- name: POSTGRES_PASSWORD
value: SovereignCloudSecurePass2026!
ports:
- containerPort: 5432
name: postgres
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: social-media
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
---
# Redis In-Memory Cache
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: social-media
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.2-alpine
ports:
- containerPort: 6379
name: redis
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: social-media
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
# Backend REST API Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
namespace: social-media
spec:
replicas: 2
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
spec:
containers:
- name: api
image: node:20-alpine
command: ["sh", "-c"]
args:
- |
cat << 'EOF' > server.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
if (req.url === '/health') {
res.end(JSON.stringify({ status: 'healthy', database: 'connected', cache: 'connected', timestamp: new Date() }));
} else if (req.url === '/api/feed') {
res.end(JSON.stringify({
platform: 'Pantech Sovereign Cloud',
posts: [
{ id: 1, author: 'Ada Lovelace', content: 'Deployed our sovereign Kubernetes cluster in af-abj-2!', likes: 42 },
{ id: 2, author: 'Cloud Architect', content: 'PostgreSQL 16 and Redis 7 running at 1ms latency.', likes: 19 }
]
}));
} else {
res.end(JSON.stringify({ message: 'Social Media API online' }));
}
});
server.listen(8080, () => console.log('API listening on 8080'));
EOF
node server.js
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: social-media
spec:
type: NodePort
selector:
app: api-service
ports:
- port: 8080
targetPort: 8080
nodePort: 30085
---
# Frontend Web UI (Next.js / HTML5)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-ui
namespace: social-media
spec:
replicas: 2
selector:
matchLabels:
app: web-ui
template:
metadata:
labels:
app: web-ui
spec:
containers:
- name: web
image: nginx:alpine
command: ["sh", "-c"]
args:
- |
cat << 'EOF' > /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pantech Social Media</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
.card { background: #1e293b; border-radius: 12px; padding: 1.5rem; max-width: 600px; margin: 0 auto 1.5rem auto; border: 1px solid #334155; }
h1 { font-size: 1.5rem; margin-top: 0; color: #38bdf8; }
.post { border-bottom: 1px solid #334155; padding: 1rem 0; }
.author { font-weight: 600; color: #94a3b8; }
.badge { display: inline-block; background: #0284c7; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: bold; }
</style>
</head>
<body>
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h1>Pantech Social Platform</h1>
<span class="badge">Live on K8s</span>
</div>
<p style="color: #94a3b8;">Sovereign Kubernetes deployment running in VPC af-abj-2.</p>
<div class="post">
<div class="author">Ada Lovelace · 5m ago</div>
<p>PostgreSQL 16 StatefulSet with automated PVC storage mounted successfully.</p>
</div>
<div class="post">
<div class="author">Platform Engineer · 12m ago</div>
<p>Redis 7.2 timeline cache operating with sub-millisecond response time.</p>
</div>
</div>
</body>
</html>
EOF
nginx -g 'daemon off;'
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-ui
namespace: social-media
spec:
type: NodePort
selector:
app: web-ui
ports:
- port: 80
targetPort: 80
nodePort: 30084Apply the manifest using kubectl:
$ kubectl apply -f social-media-app.yamlnamespace/social-media createdstatefulset.apps/postgres createdservice/postgres createddeployment.apps/redis createdservice/redis createddeployment.apps/api-service createdservice/api-service createddeployment.apps/web-ui createdservice/web-ui createdVerify that all pods reach the Running state:
$ kubectl get pods -n social-media -o wideNAME READY STATUS RESTARTS AGE IP NODEapi-service-789bfb8d5d-9kx2j 1/1 Running 0 45s 10.244.1.25 production-apps-worker-1api-service-789bfb8d5d-m4pl9 1/1 Running 0 45s 10.244.2.14 production-apps-worker-2postgres-0 1/1 Running 0 45s 10.244.1.24 production-apps-worker-1redis-6c58fb944f-q8n9v 1/1 Running 0 45s 10.244.2.13 production-apps-worker-2web-ui-54bf4d7bc9-5x289 1/1 Running 0 45s 10.244.1.26 production-apps-worker-1web-ui-54bf4d7bc9-92kl1 1/1 Running 0 45s 10.244.2.15 production-apps-worker-2Step 4: Expose Services & Port Forwarding
By default, workloads running in a VPC subnet have only private IP addresses. To access your services from the internet or your web browser, Pantech Cloud provides three exposure options.
Option A: Automated Sync via the Console
Pantech Cloud features automated public routing synchronization for Kubernetes clusters.
- Navigate to Kubernetes in the console and open your cluster.
- Select the Workloads & Services tab.
- In the Public Network Routing & Port Forwarding card, click Sync Public Routing.
- The platform automatically scans your active NodePort services and synchronizes VPC gateway forwarding rules and firewall ingress entries on your cluster's public gateway IP (for example
102.211.122.155).
Option B: Manual Gateway Port Forwarding via Static IPs
If you wish to configure or customize port forwarding manually:
- Locate the Gateway IP in the Console:
- In the left sidebar, click Network → Static IPs.
- Select the shared Static IP allocated to your cluster or VPC (e.g.,
102.211.122.155).
- Add a Port Forwarding Rule:
- In the Port forwarding section, click Add forward.
- Target VM: Select one of your cluster's worker node VMs (e.g.,
production-apps-worker-1). - Protocol:
TCP. - Public Port: Enter the port you want accessible on the internet (e.g.,
8084for web UI,8085for API). - To VM Port: Enter the service's
nodePortdefined in your manifest (e.g.,30084or30085). - Click Add forward.
- Configure Subnet Network ACL:
- Port forwarding passes traffic through the gateway, but the subnet's firewall must allow ingress.
- Go to Network → VPCs and click your VPC.
- Click the subnet hosting your Kubernetes nodes (e.g.,
k8s-subnet). - In the Network ACL section, ensure there is an ingress rule permitting inbound traffic on the target port range (e.g., rule allowing TCP on port
30084and30085from0.0.0.0/0).
Once configured, your application is publicly accessible at:
- Web Storefront / UI:
http://102.211.122.155:8084 - REST API Health Endpoint:
http://102.211.122.155:8085/health
Option C: Administrative Tunnels via kubectl port-forward
For development, testing, or database administration without exposing ports to the public internet, use standard kubectl port-forward:
# Forward PostgreSQL database locally$ kubectl -n social-media port-forward svc/postgres 5432:5432# Forward Web UI locally$ kubectl -n social-media port-forward svc/web-ui 8080:80Open http://localhost:8080 in your browser to view the application securely over the encrypted kubectl tunnel.
Step 5: Visual Workload Monitoring with Kubernetes Dashboard
Every Pantech Kubernetes cluster includes the official Kubernetes Dashboard pre-installed for rich visual monitoring of workloads, pods, PVCs, and logs.
1. Start the local proxy
In your terminal, start the secure kubectl proxy:
$ kubectl proxyStarting to serve on 127.0.0.1:8001Keep this terminal open while using the dashboard.
2. Generate an admin bearer token
In a second terminal window, request an authentication token for the admin-user service account:
$ kubectl -n kubernetes-dashboard create token admin-userCopy the generated bearer token string.
3. Open the Dashboard in your browser
Open the following URL in your web browser:
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/Select Token, paste the copied bearer token into the prompt, and click Sign in.
From the Dashboard, you can:
- View live CPU and memory utilization per pod and node.
- Stream real-time container stdout/stderr logs.
- Open interactive shell sessions inside running containers (
exec). - Edit deployment replicas, ConfigMaps, and secrets in place.
Troubleshooting
Pods remain in Pending state
Check if persistent volume claims or node resources are constrained:
$ kubectl describe pod <pod-name> -n <namespace>- Insufficient CPU/Memory: If the message says
0/2 nodes are available: 2 Insufficient memory, scale your worker nodes under Change nodes in the console. - Storage Volume Binding: Check that your PVC is bound with
kubectl get pvc -n <namespace>.
Public port forward returns connection refused or timeout
- Verify that your service is of type
NodePortand that the pods are running on the node targeted by the port forwarding rule:Terminal $ kubectl get svc -n <namespace>$ kubectl get pods -n <namespace> -o wide - Check your VPC subnet's network ACL rules. An ingress rule must explicitly permit inbound traffic to the NodePort (e.g.
30080or30084) from0.0.0.0/0. - Click Sync Public Routing in your cluster's Workloads & Services tab in the console to verify all gateway port forwarding rules are active.