Masdika Loading..
Masdika.ID

DevOps Engineer

Cloud Engineer

System Administrator

  • Home
  • Portfolio
  • Services
  • Resume
  • Skills
  • Blog
  • Contact
Masdika.ID

DevOps Engineer

Cloud Engineer

System Administrator

Download CV

Recent Posts

  • Panduan Lengkap Install K3s di Ubuntu dan Konfigurasi Remote Kubectl dari Windows PC/Laptop
  • Deploy Next.js di Kubernetes dengan Ingress + SSL Let’s Encrypt
  • Tutorial Lengkap: Membuat Cloudflare API Token & Menggunakannya di Kubernetes dengan cert-manager
  • Panduan Lengkap Deploy WordPress + Redis + MySQL di Kubernetes dengan SSL Cloudflare
  • Panduan Lengkap Install & Konfigurasi Ceph 3 Node dengan RGW (S3 Compatible)

Recent Comments

  1. Riyan on Cara Kirim Notifikasi Otomatis SSL Expired via Email (Lengkap + Bash Script)
  2. Ardian on Cara Kirim Notifikasi Otomatis SSL Expired via Email (Lengkap + Bash Script)
  3. Masdika.ID on Panduan Lengkap Install K3s di Ubuntu dan Konfigurasi Remote Kubectl dari Windows PC/Laptop
  4. Rudy on Panduan Lengkap Install K3s di Ubuntu dan Konfigurasi Remote Kubectl dari Windows PC/Laptop
  5. Firman on Deploy Next.js di Kubernetes dengan Ingress + SSL Let’s Encrypt

Categories

  • Tutorial

Masddika.BIZ.ID

  • About
  • Terms & Conditions
  • Privacy Policy
BLOG POST

Panduan Lengkap Deploy WordPress + Redis + MySQL di Kubernetes dengan SSL Cloudflare

August 23, 2025 Tutorial by Masdika.ID
Panduan Lengkap Deploy WordPress + Redis + MySQL di Kubernetes dengan SSL Cloudflare

Mau bikin website WordPress super cepat, aman, dan bisa di-scale di Kubernetes? 💡 Artikel ini akan membahas langkah demi langkah bagaimana cara deploy WordPress + MySQL + Redis di atas Kubernetes, lengkap dengan SSL Let’s Encrypt (DNS-01 via Cloudflare). 🔒✨

🛠️ 1. Persiapan Cluster Kubernetes

Sebelum mulai, pastikan sudah punya:

  • Cluster Kubernetes minimal 2 node (1 master + 1 worker).
  • Ingress Nginx Controller sudah ter-install.
  • Cert-Manager terpasang (untuk auto SSL).
  • Domain aktif di Cloudflare dengan mode proxy OFF (hanya DNS).

👉 Server specs pada contoh ini:

  • 12 vCPU + 25 GB RAM + 50 GB Disk
  • Sangat cukup untuk handle traffic besar.

📦 2. Struktur Stack Kubernetes

Stack final terdiri dari komponen berikut:

  • Namespace masdika → isolasi semua resource.
  • PersistentVolume (PV) & PersistentVolumeClaim (PVC) untuk MySQL, Redis, dan WordPress.
  • MySQL (StatefulSet) untuk database.
  • Redis (StatefulSet) untuk caching.
  • WordPress (Deployment) sebagai CMS utama.
  • Ingress dengan auto SSL dari Let’s Encrypt via Cloudflare.
  • HPA (Horizontal Pod Autoscaler) → tetap 1 replika karena PVC RWO.

🔑 3. Konfigurasi Secrets & Cloudflare API Token

Untuk validasi DNS-01, kita perlu API Token Cloudflare:

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: cert-manager
stringData:
  api-token: "TOKEN_CLOUDFLARE_ANDA"

Token ini digunakan oleh cert-manager untuk menulis record TXT otomatis ke DNS Cloudflare saat request SSL.

🐬 4. Deploy MySQL (Database)

MySQL dijalankan dalam StatefulSet agar data tetap persist di PVC.

  • Default database: wordpress
  • User: wpuser
  • Password: WpUserP4ss!

Semua environment variable diinject dari Secret agar lebih aman. 🔐

⚡ 5. Deploy Redis (Caching)

Redis digunakan untuk Object Cache di WordPress.

  • Password Redis disimpan di wp-secrets.
  • Redis berjalan dengan persistence (appendonly yes).

👉 WordPress terhubung ke Redis via Predis client:

define('WP_REDIS_HOST','redis');
define('WP_REDIS_PORT',6379);
define('WP_REDIS_CLIENT','predis');
define('WP_REDIS_PASSWORD', getenv('REDIS_PASSWORD') ?: '');

Hasilnya: query database bisa di-cache, mempercepat loading WordPress 🚀

🌐 6. Deploy WordPress (PHP + Apache)

WordPress dijalankan dengan image resmi wordpress:php8.2-apache.

Fitur tambahan:

  • Auto set URL → https://www.masdika.biz.id
  • Custom PHP limits → upload 1GB, memory limit 2GB.
  • Init container untuk fix permission file WordPress.
  • Redis Cache otomatis terhubung.

🔒 7. Ingress + SSL Auto Renew

Ingress mengatur domain + SSL:

  • Domain root masdika.biz.id redirect ke www.masdika.biz.id.
  • SSL otomatis via cert-manager + ClusterIssuer Let’s Encrypt (DNS-01 Cloudflare).
  • Upload size diatur 1024m via annotation Nginx.

Hasilnya: Website WordPress bisa diakses aman via HTTPS ✅

📈 8. Horizontal Pod Autoscaler (HPA)

Karena PVC hostPath hanya bisa 1 replika (RWO), maka HPA dibatasi ke maxReplicas: 1. Namun, resource besar server membuat 1 pod sudah sangat powerful.

Jika ingin scale-out lebih banyak pod WordPress:

  • Gunakan storage RWX (mis. NFS, CephFS, atau Longhorn).
  • Atur HPA maxReplicas ke 5–10 pod sesuai kebutuhan.

📜 9. Final YAML Lengkap

Berikut file YAML lengkap (tinggal apply saja):

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    # =========================================================
# masdika.yaml
# Stack lengkap WordPress + MySQL + Redis + Ingress (DNS-01 Cloudflare)
# Redis FIX: pakai env REDIS_* + Predis, password dari REDIS_PASSWORD
# =========================================================

# ---------- Namespace ----------
apiVersion: v1
kind: Namespace
metadata:
  name: masdika
---
# ---------- PV hostPath ----------
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-mysql-20gi
spec:
  capacity: { storage: 20Gi }
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath: { path: /srv/k8s/mysql, type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-redis-5gi
spec:
  capacity: { storage: 5Gi }
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath: { path: /srv/k8s/redis, type: DirectoryOrCreate }
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-wp-20gi
spec:
  capacity: { storage: 20Gi }
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  hostPath: { path: /srv/k8s/wp-content, type: DirectoryOrCreate }

# ---------- Secret Cloudflare API Token (namespace: cert-manager) ----------
# (Biarkan seperti ini jika token sama. Jika sudah dibuat sebelumnya dan tak ingin overwrite, hapus blok ini.)
---
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-token: "9Lxxr8Gq4Zts8yD_1Cff_iVf60OknqENuQ_whndt"

# ---------- ClusterIssuer Let's Encrypt (DNS-01 Cloudflare) ----------
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-private-key
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token

# ---------- Secrets & Config ----------
---
apiVersion: v1
kind: Secret
metadata:
  name: wp-secrets
  namespace: masdika
type: Opaque
stringData:
  MYSQL_ROOT_PASSWORD: "SuperRootP4ss!"
  MYSQL_DATABASE: "wordpress"
  MYSQL_USER: "wpuser"
  MYSQL_PASSWORD: "WpUserP4ss!"
  WORDPRESS_ADMIN_USER: "admin"
  WORDPRESS_ADMIN_PASSWORD: "AdminP4ss!"
  WORDPRESS_ADMIN_EMAIL: "[email protected]"
  REDIS_PASSWORD: "RedisP4ss!"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: php-custom-ini
  namespace: masdika
data:
  zzz-custom.ini: |
    file_uploads        = On
    upload_max_filesize = 1024M
    post_max_size       = 1024M
    memory_limit        = 2048M
    max_execution_time  = 300

# ---------- PVC ----------
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
  namespace: masdika
spec:
  accessModes: ["ReadWriteOnce"]
  resources: { requests: { storage: 20Gi } }
  volumeName: pv-mysql-20gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data
  namespace: masdika
spec:
  accessModes: ["ReadWriteOnce"]
  resources: { requests: { storage: 5Gi } }
  volumeName: pv-redis-5gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-content
  namespace: masdika
spec:
  accessModes: ["ReadWriteOnce"]
  resources: { requests: { storage: 20Gi } }
  volumeName: pv-wp-20gi

# ---------- MySQL ----------
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: masdika
  labels: { app: mysql }
spec:
  clusterIP: None
  ports: [{ port: 3306, name: mysql }]
  selector: { app: mysql }
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: masdika
spec:
  serviceName: mysql
  replicas: 1
  selector: { matchLabels: { app: mysql } }
  template:
    metadata: { labels: { app: mysql } }
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - { name: MYSQL_ROOT_PASSWORD, valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_ROOT_PASSWORD } } }
            - { name: MYSQL_DATABASE,      valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_DATABASE } } }
            - { name: MYSQL_USER,          valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_USER } } }
            - { name: MYSQL_PASSWORD,      valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_PASSWORD } } }
          args: ["--default-authentication-plugin=mysql_native_password","--innodb-buffer-pool-size=1G"]
          ports: [{ containerPort: 3306, name: mysql }]
          volumeMounts: [{ name: mysql-data, mountPath: /var/lib/mysql }]
      volumes:
        - { name: mysql-data, persistentVolumeClaim: { claimName: mysql-data } }

# ---------- Redis ----------
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: masdika
  labels: { app: redis }
spec:
  ports: [{ port: 6379, name: redis }]
  selector: { app: redis }
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: masdika
spec:
  serviceName: redis
  replicas: 1
  selector: { matchLabels: { app: redis } }
  template:
    metadata: { labels: { app: redis } }
    spec:
      containers:
        - name: redis
          image: redis:7
          env:
            - { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: wp-secrets, key: REDIS_PASSWORD } } }
          command: ["/bin/sh","-lc"]
          args: ["exec redis-server --appendonly yes --requirepass \"$REDIS_PASSWORD\""]
          ports: [{ containerPort: 6379, name: redis }]
          volumeMounts: [{ name: redis-storage, mountPath: /data }]
      volumes:
        - { name: redis-storage, persistentVolumeClaim: { claimName: redis-data } }

# ---------- WordPress (Redis FIX: REDIS_* + Predis) ----------
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  namespace: masdika
  labels: { app: wordpress }
spec:
  ports:
    - { port: 80, targetPort: 80, name: http }
  selector: { app: wordpress }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: masdika
spec:
  replicas: 1
  strategy: { type: Recreate }   # PVC hostPath = RWO → 1 pod
  selector: { matchLabels: { app: wordpress } }
  template:
    metadata: { labels: { app: wordpress } }
    spec:
      securityContext:
        fsGroup: 33
        fsGroupChangePolicy: OnRootMismatch
      initContainers:
        - name: fix-perms
          image: busybox:1.36
          command: ["sh","-c","chown -R 33:33 /var/www/html || true"]
          volumeMounts:
            - { name: wp-content, mountPath: /var/www/html }
      containers:
        - name: wordpress
          image: wordpress:php8.2-apache
          imagePullPolicy: IfNotPresent
          env:
            # DB
            - { name: WORDPRESS_DB_HOST, value: "mysql:3306" }
            - { name: WORDPRESS_DB_NAME, valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_DATABASE } } }
            - { name: WORDPRESS_DB_USER, valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_USER } } }
            - { name: WORDPRESS_DB_PASSWORD, valueFrom: { secretKeyRef: { name: wp-secrets, key: MYSQL_PASSWORD } } }

            # Redis (ENV standar plugin)
            - { name: REDIS_HOST, value: "redis" }
            - { name: REDIS_PORT, value: "6379" }
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: wp-secrets
                  key: REDIS_PASSWORD

            # Extra wp-config: aktifkan Predis & pakai REDIS_PASSWORD
            - name: WORDPRESS_CONFIG_EXTRA
              value: |
                define('WP_HOME','https://www.masdika.biz.id');
                define('WP_SITEURL','https://www.masdika.biz.id');
                define('WP_MEMORY_LIMIT','512M');
                if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; }
                define('WP_REDIS_HOST','redis');
                define('WP_REDIS_PORT',6379);
                define('WP_REDIS_CLIENT','predis');
                define('WP_REDIS_PASSWORD', getenv('REDIS_PASSWORD') ?: '' );
                define('WP_CACHE_KEY_SALT','masdika_');

          ports: [{ containerPort: 80, name: http }]
          readinessProbe: { httpGet: { path: "/", port: http }, initialDelaySeconds: 30, periodSeconds: 10, failureThreshold: 12 }
          livenessProbe:  { httpGet: { path: "/", port: http }, initialDelaySeconds: 90, periodSeconds: 10, failureThreshold: 6 }
          resources:
            requests: { cpu: "500m", memory: "1Gi" }
            limits:   { cpu: "3",    memory: "6Gi" }
          volumeMounts:
            - { name: wp-content, mountPath: /var/www/html }
            - { name: phpini,     mountPath: /usr/local/etc/php/conf.d/zzz-custom.ini, subPath: zzz-custom.ini }
          envFrom: [{ secretRef: { name: wp-secrets } }]
      volumes:
        - { name: wp-content, persistentVolumeClaim: { claimName: wp-content } }
        - { name: phpini,     configMap: { name: php-custom-ini } }

# ---------- Ingress (TLS root+www, redirect → www) ----------
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress
  namespace: masdika
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - masdika.biz.id
        - www.masdika.biz.id
      secretName: masdika-tls
  rules:
    - host: www.masdika.biz.id
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: wordpress, port: { number: 80 } }

# ---------- Certificate eksplisit ----------
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: masdika-tls
  namespace: masdika
spec:
  secretName: masdika-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - masdika.biz.id
    - www.masdika.biz.id

# ---------- HPA (max 1 karena PVC RWO) ----------
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: wordpress
  namespace: masdika
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: wordpress }
  minReplicas: 1
  maxReplicas: 1
  metrics:
    - type: Resource
      resource: { name: cpu,    target: { type: Utilization, averageUtilization: 60 } }
    - type: Resource
      resource: { name: memory, target: { type: Utilization, averageUtilization: 70 } }

🎯 10. Hasil Akhir

✅ WordPress online di https://www.masdika.biz.id
✅ SSL otomatis dari Let’s Encrypt via Cloudflare
✅ Redis Object Cache aktif → performa meningkat
✅ MySQL persistent & secure
✅ Stack mudah di-maintain (1 file YAML saja)

✨ Kesimpulan

Dengan setup ini, kita punya WordPress super cepat, aman, scalable, dan hemat biaya di atas Kubernetes. Semua resource dipisahkan dalam namespace, SSL auto renew, Redis cache aktif, dan siap dipakai untuk production. 💪🔥

👉 Next step: bisa ditambah monitoring (Prometheus + Grafana), backup MySQL otomatis,

Share:
Tags: kubernetesmysqlredissslwordpress
Related Posts
Panduan Lengkap Install K3s di Ubuntu dan Konfigurasi Remote Kubectl dari Windows PC/Laptop

🚀 Apa itu K3s? K3s adalah distribusi Kubernetes ringan dari Rancher yang dirancang untuk mempermudah proses instalasi dan penggunaan Kubernetes….

Deploy Next.js di Kubernetes dengan Ingress + SSL Let’s Encrypt

Buat kamu yang lagi Deploy aplikasi dengan Next.js dan pengen jalan di Kubernetes dengan domain custom + SSL otomatis, artikel…

Post navigation

Prev
Next
3 Comments
  • Adiguna 6:06 pm August 23, 2025 Reply

    Thanks for sharing mas

    • Masdika.ID 5:27 pm August 25, 2025 Reply

      sama sama mas

  • Dany 2:00 pm August 25, 2025 Reply

    Mantap mas

Write a comment Cancel Reply


© 2025 www.masdika.id — Semua hak cipta dilindungi