← กลับ
KubernetesConfigMapSecretTroubleshooting

Debug Environment Variable ใน Pod ทำไมค่าถึงไม่มา

ตั้ง env ใน Deployment แล้วแต่ app ยังหา DATABASE_URL ไม่เจอ — สาเหตุที่เป็นไปได้และวิธีตรวจสอบ ConfigMap, Secret, envFrom, override order

2026-01-12อ่าน 5 นาทีใหม่

อาการ

Error: DATABASE_URL is not defined
   at config.ts:12

แต่ใน Deployment มีตั้งไว้:

env:
  - name: DATABASE_URL
    value: "postgresql://..."

หรือใน ConfigMap:

data:
  DATABASE_URL: "postgresql://..."

ทำไมถึงไม่มา?

วิธีดู env ที่ Pod รับจริง

อันดับแรก — ตรวจที่ pod ตัวจริง ไม่ใช่ที่ manifest

kubectl exec -it myapp-abc123 -- env

หรือ:

kubectl exec -it myapp-abc123 -- env | grep DATABASE

ถ้าไม่เห็น DATABASE_URL — env ไม่ได้ถูก inject

ถ้าเห็น แต่ค่าผิด — ConfigMap/Secret ผิด

สาเหตุที่เป็นไปได้

1. envFrom ชี้ไป ConfigMap/Secret ที่ไม่มี

envFrom:
  - configMapRef:
      name: myapp-config       # ← ชื่อนี้มีจริงมั้ย

ตรวจ:

kubectl get configmap -n production
kubectl get secret -n production

ถ้าไม่มี — apply ก่อน หรือเช็ค namespace

2. ConfigMap อยู่คนละ namespace กับ Pod

ConfigMap ของ namespace "default" — Pod ใน namespace "production" เรียกไม่ได้

ตรวจ:

# Pod อยู่ namespace ไหน
kubectl get pod myapp-abc123 -o jsonpath='{.metadata.namespace}'

# ConfigMap อยู่ namespace ไหน
kubectl get configmap myapp-config -A

ทั้งคู่ต้องอยู่ namespace เดียวกัน — ถ้าไม่ใช่ ก็ apply ConfigMap ใน namespace เดียวกับ Pod

3. Key ใน ConfigMap ไม่ตรง

# ConfigMap
data:
  DATABASE_URL: "..."          # ← key เป็น DATABASE_URL

# Deployment
env:
  - name: DATABASE_URL
    valueFrom:
      configMapKeyRef:
        name: myapp-config
        key: database_url      # ← lowercase ไม่ตรง

ตรวจ:

kubectl get configmap myapp-config -o yaml | grep -A 10 data:

4. Pod ยังไม่ได้ rolling update หลังเปลี่ยน ConfigMap

K8s ไม่ auto-restart pod เมื่อ ConfigMap เปลี่ยน

# ดูว่า pod เกิดเมื่อไหร่
kubectl get pod myapp-abc123 -o jsonpath='{.metadata.creationTimestamp}'

# ดู ConfigMap update เมื่อไหร่
kubectl get configmap myapp-config -o jsonpath='{.metadata.creationTimestamp}'

ถ้า ConfigMap ใหม่กว่า Pod — Pod ยังใช้ค่าเก่า

แก้:

kubectl rollout restart deployment myapp -n production

ป้องกันถาวร — ใช้ Reloader (อ่าน ConfigMap Production)

5. Order ของ env ใน manifest

env ที่กำหนดทีหลังจะ override อันก่อน:

envFrom:
  - configMapRef:
      name: myapp-config       # มี LOG_LEVEL=info
env:
  - name: LOG_LEVEL
    value: "debug"             # อันนี้ทับ

ลำดับใน K8s:

  1. envFrom (ทำตามลำดับใน list)
  2. env (override envFrom)

ใช้กฎนี้ override ค่าตัวเดียวจาก ConfigMap

6. Secret ใช้ stringData vs data ผิด

# ผิด — base64 ใส่ผิด
apiVersion: v1
kind: Secret
data:
  DATABASE_URL: "postgresql://..."   # ← ต้อง base64 encode

# ถูก
data:
  DATABASE_URL: "cG9zdGdyZXNxbDovLy4uLg=="

# หรือใช้ stringData (ระบบ encode ให้)
stringData:
  DATABASE_URL: "postgresql://..."

ตรวจ:

kubectl get secret myapp-secret -o yaml

decode ดูค่า:

kubectl get secret myapp-secret -o jsonpath='{.data.DATABASE_URL}' | base64 -d

7. Application read env ผิด

ตรวจที่ app code:

// ผิด — ไม่มี value default
const url = process.env.DATABSE_URL    // typo!

// ผิด — Next.js ฝั่ง browser ต้อง prefix
const apiKey = process.env.API_KEY     // browser อ่านไม่ได้

// ถูก
const url = process.env.DATABASE_URL
const apiKey = process.env.NEXT_PUBLIC_API_KEY

โดยเฉพาะ Next.js — env ที่ใช้ฝั่ง client ต้อง prefix NEXT_PUBLIC_

8. Container ใน pod คนละตัว

Pod มีหลาย container — env ของ A ไม่อยู่ใน B

ตรวจ:

kubectl exec -it myapp-abc123 -c app -- env
kubectl exec -it myapp-abc123 -c sidecar -- env

ถ้า env ต้องอยู่ทั้ง 2 — ใส่ที่ระดับ pod (spec.containers[].env) ของแต่ละ container

K8s ไม่มี "shared env" ระหว่าง container ใน pod

9. Init container vs main container

Init container กับ main container เห็น env แยกกัน

spec:
  initContainers:
    - name: setup
      env:                          # init เท่านั้น
        - name: SETUP_KEY
          value: "..."
  containers:
    - name: app
      env:                          # main เท่านั้น
        - name: APP_KEY
          value: "..."

SETUP_KEY ไม่อยู่ใน main container

10. value ว่าง vs ไม่ inject

env:
  - name: API_KEY
    value: ""          # มี env ค่าว่าง

vs ไม่มี env key เลย — env ไม่ถูก inject

ใน app:

process.env.API_KEY === ""              // case 1
process.env.API_KEY === undefined       // case 2

ถ้า code เช็ค if (!process.env.API_KEY) — ทั้งคู่ผ่าน แต่บาง code เช็ค === undefined เท่านั้น

วิธี Debug ที่แนะนำ

Step 1: ดู env ทั้งหมดใน pod

kubectl exec myapp-abc123 -- env | sort

ถ้าไม่มี shell — kubectl exec รัน command ตรง:

kubectl exec myapp-abc123 -- printenv DATABASE_URL

Step 2: ดู spec ของ pod ที่กำลังรัน

kubectl get pod myapp-abc123 -o yaml | grep -A 50 "containers:"

อ่าน env: และ envFrom: — ตรงกับที่ manifest มั้ย

ถ้าไม่ตรง — Pod ยังใช้ spec เก่า (ไม่ได้ rolling update)

Step 3: ตรวจ ConfigMap / Secret

kubectl describe configmap myapp-config
kubectl describe secret myapp-secret

ดู Data: section — มี key ที่ต้องการมั้ย ค่าถูกต้องมั้ย

Step 4: ดู Events ของ Pod

kubectl describe pod myapp-abc123 | grep -A 20 Events

อาจเห็น:

Warning  Failed  Error: secret "myapp-secret" not found

Step 5: ทดสอบด้วย Pod ใหม่

kubectl run test --rm -it --image=busybox \
  --env-from-configmap=myapp-config \
  --restart=Never -- env

(ใช้กับบาง K8s version) หรือสร้าง manifest ทดสอบ:

apiVersion: v1
kind: Pod
metadata:
  name: env-test
spec:
  containers:
    - name: test
      image: busybox
      command: ["sh", "-c", "env && sleep 3600"]
      envFrom:
        - configMapRef:
            name: myapp-config

Pattern ที่ดี

Validate ที่ app start

import { z } from 'zod'

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  PORT: z.coerce.number().default(3000),
})

const env = envSchema.parse(process.env)

ถ้า env ขาด/format ผิด → app crash ตอน start ทันที — ดีกว่า silent

ใช้ envFrom เป็นหลัก

envFrom:
  - configMapRef:
      name: myapp-config
  - secretRef:
      name: myapp-secret

ดี: ไม่ต้องเขียน env ทีละตัวใน Deployment เพิ่ม/ลด config ที่ ConfigMap จุดเดียว

ใส่ default ใน app

const port = parseInt(process.env.PORT ?? '3000', 10)
const logLevel = process.env.LOG_LEVEL ?? 'info'

env ขาดบางตัว — app ยังทำงานต่อได้

Cheat Sheet

# ดู env จริงใน pod
kubectl exec <pod> -- env | sort

# ดู spec ของ pod
kubectl get pod <pod> -o yaml | grep -A 50 containers:

# ตรวจ ConfigMap/Secret
kubectl get configmap <name> -o yaml
kubectl get secret <name> -o yaml

# decode secret
kubectl get secret <name> -o jsonpath='{.data.<KEY>}' | base64 -d

# rolling restart หลังแก้ ConfigMap
kubectl rollout restart deployment <name>

สรุป

90% ของ env ไม่มา = pod ใช้ spec เก่า → rolling restart

อีก 10%:

  • ConfigMap key ผิด
  • ConfigMap คนละ namespace
  • Override order ใน manifest
  • App typo

ตรวจที่ kubectl exec <pod> -- env ก่อนเสมอ — ถ้าเห็นใน pod ก็แสดงว่า K8s inject แล้ว ปัญหาอยู่ที่ app

อ่านต่อ: ConfigMap Production — pattern ที่ป้องกันปัญหานี้ตั้งแต่ต้น

← ดูบทความอื่น