Documentation

esc
    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:

    1. Creating a cluster and setting up your local kubectl environment.
    2. Deploying real-world, multi-tier application blueprints (including stateful databases, Redis caches, REST APIs, and modern web frontends).
    3. Exposing containerized services publicly using VPC gateway port forwarding and NodePort services.
    4. 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:

    1. In the console, navigate to Kubernetes in the left sidebar and click Create cluster.
    2. Zone: Select your deployment zone (for example, af-abj-2).
    3. VPC and Subnet: Choose the private VPC network and subnet where your nodes will reside (for example, k8s-subnet in prod-vpc).
    4. Kubernetes Version: Choose the latest stable release (such as 1.31.1).
    5. Node Plan: Choose the VM specification for your nodes (for example, std-2c-4g for 2 vCPUs and 4 GB RAM).
    6. Worker Nodes: Choose the desired worker count (for example, 2 nodes).
    7. Cluster Name: Provide a unique, lowercase name (for example, production-apps).
    8. 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:

    Terminal
    $ chmod 600 ~/Downloads/production-apps-kubeconfig.yaml$ export KUBECONFIG=~/Downloads/production-apps-kubeconfig.yaml

    2. Verify cluster nodes

    Test connectivity to the Kubernetes API server and verify all nodes are Ready:

    Terminal
    $ 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 LTS

    Step 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 StatefulSet with an automated PersistentVolumeClaim.
    • 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:

    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: 30084

    Apply the manifest using kubectl:

    Terminal
    $ 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 created

    Verify that all pods reach the Running state:

    Terminal
    $ 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-2

    Step 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.

    1. Navigate to Kubernetes in the console and open your cluster.
    2. Select the Workloads & Services tab.
    3. In the Public Network Routing & Port Forwarding card, click Sync Public Routing.
    4. 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:

    1. 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).
    2. 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., 8084 for web UI, 8085 for API).
      • To VM Port: Enter the service's nodePort defined in your manifest (e.g., 30084 or 30085).
      • Click Add forward.
    3. 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 30084 and 30085 from 0.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:

    Terminal
    # 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:80

    Open 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:

    Terminal
    $ kubectl proxyStarting to serve on 127.0.0.1:8001

    Keep 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:

    Terminal
    $ kubectl -n kubernetes-dashboard create token admin-user

    Copy the generated bearer token string.

    3. Open the Dashboard in your browser

    Open the following URL in your web browser:

    Code
    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:

    Terminal
    $ 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

    1. Verify that your service is of type NodePort and 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
    2. Check your VPC subnet's network ACL rules. An ingress rule must explicitly permit inbound traffic to the NodePort (e.g. 30080 or 30084) from 0.0.0.0/0.
    3. Click Sync Public Routing in your cluster's Workloads & Services tab in the console to verify all gateway port forwarding rules are active.

    Was this page helpful?

    Updated