Home > Blog > tech

cert-manager คืออะไร? สอนจัดการ TLS Certificate อัตโนมัติบน Kubernetes 2026

kubernetes cert manager tls guide
cert-manager TLS Certificate Kubernetes 2026
2026-04-16 | tech | 3600 words

การจัดการ TLS Certificate บน Kubernetes เป็นงานที่น่าเบื่อและเสี่ยงต่อข้อผิดพลาด: ต้อง Generate, ต้อง Renew ก่อนหมดอายุ, ต้อง Deploy ให้ถูก Secret cert-manager แก้ปัญหาทั้งหมดนี้ ทำให้การจัดการ TLS Certificate เป็น อัตโนมัติ 100%

ทำไมต้อง cert-manager?

ปัญหาไม่มี cert-managerมี cert-manager
ออก Certificatecertbot manual / ทำเองอัตโนมัติ (Issuer/ClusterIssuer)
Renewตั้ง Cron / ลืมแล้ว ExpireAuto-renew ก่อนหมดอายุ 30 วัน
WildcardDNS Challenge ด้วยมือAuto DNS Challenge
Multiple Domainsจัดการแยกแต่ละโดเมนCertificate Resource ต่อโดเมน
Ingress Integrationต้อง Mount Secret เองAnnotation-based (อัตโนมัติ)
Monitoringไม่มี (ลืม Renew = Downtime)Prometheus Metrics + Alert

ติดตั้ง cert-manager ด้วย Helm

# ติดตั้ง cert-manager:
helm repo add jetstack https://charts.jetstack.io
helm repo update

# ติดตั้งพร้อม CRDs:
helm install cert-manager jetstack/cert-manager \
    --namespace cert-manager \
    --create-namespace \
    --version v1.14.0 \
    --set installCRDs=true

# ตรวจสอบ:
kubectl get pods -n cert-manager
# cert-manager-xxxx           1/1  Running
# cert-manager-webhook-xxxx   1/1  Running
# cert-manager-cainjector-xxx 1/1  Running

# ทดสอบด้วย cmctl:
cmctl check api
# → The cert-manager API is ready

Issuer vs ClusterIssuer

คุณสมบัติIssuerClusterIssuer
ScopeNamespace เดียวทั้ง Cluster
ใช้เมื่อแยก Issuer ต่อ Namespaceใช้ Issuer เดียวทุก Namespace
ใช้ใน Ingresscert-manager.io/issuercert-manager.io/cluster-issuer
แนะนำMulti-tenant Clusterส่วนใหญ่ใช้ตัวนี้
# ClusterIssuer สำหรับ Let's Encrypt (Staging):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-key
    solvers:
    - http01:
        ingress:
          class: nginx

# ClusterIssuer สำหรับ Let's Encrypt (Production):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx

# Apply:
kubectl apply -f clusterissuer.yaml

# ตรวจสอบ:
kubectl get clusterissuer
# NAME                 READY   AGE
# letsencrypt-staging  True    1m
# letsencrypt-prod     True    1m

ACME Challenges: HTTP-01 vs DNS-01

คุณสมบัติHTTP-01DNS-01
วิธีการสร้างไฟล์ที่ /.well-known/acme-challenge/สร้าง TXT Record ใน DNS
ต้องการIngress + Port 80 เปิดDNS Provider API Access
Wildcardไม่ได้ได้
ง่ายในการ Setupง่ายกว่าต้อง Config DNS Provider
Internal Clusterไม่ได้ (ต้อง Public)ได้ (ต้อง DNS เข้าถึง)
แนะนำSingle Domain, ง่ายWildcard, Multiple Subdomains

Let's Encrypt Staging vs Production

# ทำไมต้องใช้ Staging ก่อน?
#
# Let's Encrypt Production มี Rate Limits:
# - 50 Certificates per Registered Domain per week
# - 5 Duplicate Certificates per week
# - 300 New Orders per account per 3 hours
#
# Staging ไม่มี Rate Limits (เหมาะสำหรับ Testing)
# แต่ Certificate ไม่ Trusted (Browser จะเตือน)
#
# ขั้นตอนที่ถูกต้อง:
# 1. ตั้งค่ากับ letsencrypt-staging ก่อน
# 2. ตรวจสอบว่า Certificate ออกได้สำเร็จ
# 3. เปลี่ยนเป็น letsencrypt-prod
# 4. ลบ Certificate/Secret เก่าของ Staging
# 5. cert-manager จะออก Certificate ใหม่จาก Production

Certificate Resource

# สร้าง Certificate Resource โดยตรง:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: production
spec:
  secretName: example-com-tls-secret
  duration: 2160h    # 90 วัน
  renewBefore: 720h  # Renew ก่อนหมดอายุ 30 วัน
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: example.com
  dnsNames:
  - example.com
  - www.example.com
  - api.example.com

# ตรวจสอบ:
kubectl get certificate -n production
# NAME              READY   SECRET                    AGE
# example-com-tls   True    example-com-tls-secret    5m

kubectl describe certificate example-com-tls -n production
# → ดู Status, Events, Conditions

# ดู Secret:
kubectl get secret example-com-tls-secret -n production
# → มี tls.crt และ tls.key

cert-manager + Ingress (Annotation-based)

# วิธีง่ายที่สุด: ใช้ Annotation ใน Ingress
# cert-manager จะสร้าง Certificate อัตโนมัติ!

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"  # ← แค่เพิ่มบรรทัดนี้!
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    - www.example.com
    secretName: example-com-tls    # cert-manager สร้างให้อัตโนมัติ
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80

# แค่เพิ่ม annotation + tls section
# cert-manager ทำทุกอย่างให้:
# 1. สร้าง Certificate Resource
# 2. สร้าง CertificateRequest
# 3. สร้าง Order
# 4. สร้าง Challenge (HTTP-01)
# 5. ออก Certificate
# 6. เก็บใน Secret
# 7. Auto-Renew ก่อนหมดอายุ

Wildcard Certificates with DNS-01

# Wildcard Certificate ต้องใช้ DNS-01 Challenge
# ตัวอย่างกับ Cloudflare:

# 1. สร้าง Cloudflare API Token Secret:
kubectl create secret generic cloudflare-api-token \
    --from-literal=api-token=YOUR_CLOUDFLARE_API_TOKEN \
    -n cert-manager

# 2. ClusterIssuer กับ DNS-01 (Cloudflare):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-dns-key
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

# 3. Certificate (Wildcard):
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: production
spec:
  secretName: wildcard-example-com-tls
  issuerRef:
    name: letsencrypt-prod-dns
    kind: ClusterIssuer
  dnsNames:
  - example.com
  - "*.example.com"    # ← Wildcard!

# DNS-01 Providers ที่ cert-manager Support:
# Cloudflare, Route53 (AWS), Cloud DNS (GCP),
# Azure DNS, DigitalOcean, Akamai,
# RFC2136 (BIND), Webhook (Custom)

Monitoring Certificate Expiry

# cert-manager มี Prometheus Metrics:
# cert-manager เปิด Metrics ที่ port 9402

# Metrics สำคัญ:
# certmanager_certificate_ready_status
# certmanager_certificate_expiration_timestamp_seconds
# certmanager_certificate_renewal_timestamp_seconds

# Prometheus Alert Rule:
# alert: CertificateExpiringSoon
# expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7*24*60*60
# for: 1h
# labels:
#   severity: warning
# annotations:
#   summary: "Certificate expiring in less than 7 days"

# ตรวจสอบด้วย cmctl:
cmctl status certificate example-com-tls -n production
# → แสดง Expiry Date, Renewal Date, Status

# ตรวจสอบด้วย kubectl:
kubectl get certificate --all-namespaces
# → ดู READY status ทุก Certificate

cert-manager + Istio

# ใช้ cert-manager เป็น CA สำหรับ Istio mTLS:
#
# ปกติ Istio ใช้ istiod เป็น CA (Self-Signed)
# ใช้ cert-manager แทน → จัดการ CA ได้ดีกว่า

# 1. ติดตั้ง istio-csr:
helm install istio-csr jetstack/cert-manager-istio-csr \
    --namespace cert-manager \
    --set "app.tls.rootCAFile=/var/run/secrets/istio-csr/ca.pem"

# 2. Istio ใช้ cert-manager CA:
# Istio workload certificates จะถูก Sign โดย cert-manager
# → จัดการ CA Rotation ได้
# → ใช้ External CA (Vault, AWS PCA) ได้

cert-manager for Internal CA

# ใช้ cert-manager สำหรับ Internal Certificate (Self-Signed CA):

# 1. สร้าง Self-Signed Issuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}

# 2. สร้าง CA Certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: internal-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: Internal CA
  secretName: internal-ca-secret
  duration: 87600h    # 10 ปี
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer

# 3. สร้าง CA Issuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: internal-ca-issuer
spec:
  ca:
    secretName: internal-ca-secret

# ใช้ internal-ca-issuer ออก Certificate สำหรับ Service ภายใน
# เหมาะกับ: Microservices mTLS, Internal APIs, Dev/Staging

Troubleshooting

# Certificate Not Ready? ทำอย่างไร:
#
# 1. ตรวจ Certificate Status:
kubectl describe certificate MY_CERT -n MY_NAMESPACE
# → ดู Conditions, Events

# 2. ตรวจ CertificateRequest:
kubectl get certificaterequest -n MY_NAMESPACE
kubectl describe certificaterequest MY_CERT-xxxxx -n MY_NAMESPACE

# 3. ตรวจ Order:
kubectl get order -n MY_NAMESPACE
kubectl describe order MY_CERT-xxxxx -n MY_NAMESPACE

# 4. ตรวจ Challenge:
kubectl get challenge -n MY_NAMESPACE
kubectl describe challenge MY_CERT-xxxxx -n MY_NAMESPACE
# → ดูว่า Challenge Pass หรือไม่

# Common Issues:
# =============================================
# 1. Challenge Failed (HTTP-01):
#    - Port 80 ถูก Block?
#    - Ingress Controller ทำงานปกติ?
#    - DNS ชี้มาที่ Cluster?
#    → ทดสอบ: curl http://example.com/.well-known/acme-challenge/test
#
# 2. Challenge Failed (DNS-01):
#    - API Token ถูกต้อง?
#    - API Token มีสิทธิ์แก้ไข DNS?
#    - DNS Propagation ช้า?
#    → ทดสอบ: dig TXT _acme-challenge.example.com
#
# 3. Rate Limited:
#    - ใช้ Staging ก่อนทดสอบ!
#    - ดู: https://letsencrypt.org/docs/rate-limits/
#
# 4. Secret ไม่ถูกสร้าง:
#    - cert-manager Pod มี Permission?
#    - RBAC ถูกต้อง?
#    → kubectl logs -n cert-manager deploy/cert-manager

cert-manager + external-dns

# ใช้ cert-manager ร่วมกับ external-dns:
# external-dns สร้าง DNS Record อัตโนมัติ
# cert-manager ออก Certificate อัตโนมัติ
#
# Flow:
# 1. สร้าง Ingress → external-dns สร้าง DNS Record
# 2. DNS Record ชี้มาที่ Cluster
# 3. cert-manager ออก Certificate (HTTP-01 Challenge ผ่าน)
# → ทุกอย่างอัตโนมัติ!

# Ingress ที่ใช้ทั้ง external-dns + cert-manager:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    external-dns.alpha.kubernetes.io/hostname: "app.example.com"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-example-com-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80

# แค่ Apply Ingress → DNS + TLS Certificate ครบอัตโนมัติ!

Best Practices

Practiceคำอธิบาย
ใช้ Staging ก่อนทดสอบกับ Let's Encrypt Staging ก่อนเสมอ
ใช้ ClusterIssuerส่วนใหญ่ใช้ ClusterIssuer ดีกว่า Issuer
Monitor Expiryตั้ง Prometheus Alert สำหรับ Certificate ใกล้หมดอายุ
ใช้ Annotation-basedใส่ Annotation ใน Ingress แทนสร้าง Certificate Resource แยก
Wildcard สำหรับ Subdomains มากถ้ามี Subdomain > 5 ตัว ใช้ Wildcard ดีกว่า
Backup CA Keyถ้าใช้ Internal CA → Backup Secret ของ CA Key
RBACจำกัดสิทธิ์ cert-manager ให้เฉพาะ Namespace ที่ต้องการ
Version Pinningใช้ Version ที่เฉพาะเจาะจง (ไม่ใช่ latest)
สำคัญ: อย่าลืมตั้ง email ที่ใช้จริงใน ClusterIssuer เพราะ Let's Encrypt จะส่ง Email เตือนก่อน Certificate หมดอายุ (กรณี cert-manager ทำงานผิดพลาด)

สรุป

cert-manager เป็นเครื่องมือที่ขาดไม่ได้สำหรับ Kubernetes Production ช่วยจัดการ TLS Certificate ตั้งแต่การออก Certificate, Renew อัตโนมัติ, จนถึง Monitoring ทั้งหมดนี้เป็นอัตโนมัติ 100% ไม่ต้องกังวลเรื่อง Certificate Expire อีกต่อไป เพียงตั้งค่า ClusterIssuer + Annotation ใน Ingress แล้ว cert-manager จัดการให้ทุกอย่าง


Back to Blog | iCafe Forex | SiamLanCard | Siam2R