Cloud-Init file to create jumphost

ตัวอย่าง file cloud-init สำหรับสร้าง jumphost โดยใช้ rocky linux os

file นี้จะทำการสร้าง user nutanix ติดตั้ง docker, kubectl และ code server เพื่อให้สามารถใช้ shell ผ่าน vscode

Shell
#cloud-config
ssh_pwauth: true
users:
- name: nutanix
passwd: nutanix/4u
groups: users,wheel
shell: /bin/bash
lock-passwd: false
sudo: ['ALL=(ALL) NOPASSWD:ALL']
chpasswd:
list: |
nutanix:nutanix/4u
root:nutanix/4u
expire: False
runcmd:
- echo "Configuring SSH..."
# Ensure PasswordAuthentication is enabled
- sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config # Delete existing line
- echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config # Add new line
# Ensure PermitRootLogin is set to yes
- sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config # Delete existing line
- echo "PermitRootLogin yes" >> /etc/ssh/sshd_config # Add new line
# Restart SSH Service
- systemctl restart sshd
- echo "SSH configuration updated and service restarted."
- '[ ! -f "/etc/yum.repos.d/nutanix_rocky9.repo" ] || mv -f /etc/yum.repos.d/nutanix_rocky9.repo /etc/yum.repos.d/nutanix_rocky9.repo.disabled'
- dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
- dnf -y install git docker-ce docker-ce-cli containerd.io
- systemctl --now enable docker
- usermod -aG docker nutanix
- 'curl -Lo /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl'
- chmod +x /usr/local/bin/kubectl
- 'curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash'
- 'su - nutanix -c "curl -fsSL https://raw.githubusercontent.com/pkhamdee/nkp-tools/main/install-tools.sh | bash"'
- eject

สำหรับ https://raw.githubusercontent.com/pkhamdee/nkp-tools/main/install-tools.sh จะเป็นการ deploy code server เพื่อใช้ linux terminal และจัดการไฟล์ผ่านทาง vscode ได้

Service Mesh with ISTIO

เพื่อที่จะใช้งาน istio ใน Nutanix Kubernetes Platform จะต้องทำการ enable service ที่เกี่ยวข้องคือ prometheus monitoring ใน menu application

จากนั้นทำการ enable istio ตามด้วย jaeger และ kiali

สำหรับ kiali จะต้อง copy configuration เพิ่มเติมโดยเข้าไปที่ view detail

และ copy ส่วนที่เป็น configuration ที่ menu overview ดังรูป

ในขั้นตอน enable ให้ใส่ configuration ก่อนที่จะ enable ดังรูป

หลังจาก enable เสร็จแล้วให้รอจนกว่า service จะ install เรียบร้อย ตรวจสอบได้จาก cli ตามตัวอย่าง

[nutanix@harbor ~]$ kubectl get pod -A | grep istio
istio-helm-gateway-ns istio-helm-ingressgateway-79df9868c8-5vkzb 1/1 Running 0 102m
istio-helm-gateway-ns istio-helm-ingressgateway-79df9868c8-6shfm 1/1 Running 0 102m
istio-helm-system istiod-istio-helm-545d9895f9-l56hv 1/1 Running 0 102m
istio-helm-system istiod-istio-helm-545d9895f9-p9bzq 1/1 Running 0 102m
istio-system jaeger-jaeger-operator-759ffd786-vf7wc 1/1 Running 0 104m
istio-system jaeger-jaeger-operator-jaeger-6cb7cfb9f-l4xph 1/1 Running 0 104m
istio-system kiali-kiali-operator-c6649cd8f-t7k98 1/1 Running 0 102m
kommander-default-workspace istio-helm-pre-install-kt59f 0/1 Completed 0 105m
kube-system istio-cni-node-cwff7 1/1 Running 0 103m
kube-system istio-cni-node-fhnz9 1/1 Running 0 103m
kube-system istio-cni-node-h8szk 1/1 Running 0 103m
kube-system istio-cni-node-s2qf4 1/1 Running 0 103m

เนื่องจาก istio และ jaeger อยู่ใน workspace ที่แตกต่างกัน จะต้องมีการ update configmap เพิ่มเติม โดยใช้ cli

kubectl edit cm istio-istio-helm -n istio-helm-system

เพิ่มส่วนของ tracing ตามตัวอย่าง

YAML
apiVersion: v1
kind: ConfigMap
data:
mesh: |-
enableTracing: true
defaultConfig:
discoveryAddress: istiod-istio-helm.istio-helm-system.svc:15012
# Tracing belongs here for global configuration
tracing:
sampling: 100.0
zipkin:
address: jaeger-jaeger-operator-jaeger-collector.istio-system.svc.cluster.local:9411
defaultProviders:
metrics:
- prometheus
enablePrometheusMerge: true
rootNamespace: istio-helm-system
trustDomain: cluster.local
# ... rest of metadata

ทดสอบใช้งาน istio ในแบบ sidecare auto injection จะต้องทำการ label name space ด้วย istio.io/rev=istio-helm

ทำการสร้าง project ใน nkp โดยกรอกข้อมูลและต้องกรอกข้อมูล namespace labels จากนั้นเลือก cluster ตามตัวอย่าง

เมื่อสร้างเสร็จแล้ว เข้าไป Continuous Deployment (CD) และกดปุ่ม Add GitOps Source

กรอกข้อมูลตามตัวอย่าง และกรอกข้อมูล Repository URL เป็น https://github.com/pkhamdee/sockshop จากนั้นกด Save

จากนั้น nkp จะทำการ deploy application ลงใน namespace demo (ชื่อตรงกับ project) โดย FluxCD ซึ่งเป็น service ที่ nkp ใช้สำหรับการทำ GitOps จะ download yaml file ตาม url ที่กำหนดและ deploy application ให้อัตโนมัติ

สามารถตรวจสอบสถานะการ deploy ได้จาก cli

[nutanix@harbor ~]$ kubectl get pod -n demo
NAME READY STATUS RESTARTS AGE
carts-88b4ddf98-p5gqh 2/2 Running 0 5m39s
carts-db-7d6d697d94-5cvxk 2/2 Running 0 5m39s
catalogue-84b5874db7-rtxkf 2/2 Running 0 5m39s
catalogue-db-64d88d46ff-nkrvb 2/2 Running 0 5m39s
front-end-d87486986-8n6pc 2/2 Running 0 5m39s
orders-8587749646-8m9wb 2/2 Running 0 5m39s
orders-db-8458b5ddb4-xtv5l 2/2 Running 0 5m38s
payment-6b49f65444-4hq97 2/2 Running 0 5m38s
queue-master-686b7bf644-fw2qq 2/2 Running 0 5m38s
rabbitmq-6d679fd595-sqhs5 3/3 Running 0 5m38s
session-db-6cfcf8985d-9hph4 2/2 Running 0 5m38s
shipping-5b674b9d94-42n48 2/2 Running 0 5m37s
user-57c89fbbf4-nzt4d 2/2 Running 0 5m37s
user-db-5c748bc594-mphsx 2/2 Running 0 5m37s

ตรวจสอบว่า envoy sidecar ทำงานปกติ ด้วย kubectl cli โดยจะต้องมี log แสดงว่า “Envoy proxy is ready”

[nutanix@harbor ~]$ kubectl logs carts-88b4ddf98-p5gqh -c istio-proxy -n demo
2026-01-30T14:08:29.823282Z info FLAG: --concurrency="0"
2026-01-30T14:08:29.823327Z info FLAG: --domain="demo.svc.cluster.local"
2026-01-30T14:08:29.823332Z info FLAG: --help="false"
2026-01-30T14:08:29.823335Z info FLAG: --log_as_json="false"
2026-01-30T14:08:29.823337Z info FLAG: --log_caller=""
2026-01-30T14:08:29.823339Z info FLAG: --log_output_level="default:info"
2026-01-30T14:08:29.823341Z info FLAG: --log_rotate=""
2026-01-30T14:08:29.823344Z info FLAG: --log_rotate_max_age="30"
2026-01-30T14:08:29.823346Z info FLAG: --log_rotate_max_backups="1000"
2026-01-30T14:08:29.823348Z info FLAG: --log_rotate_max_size="104857600"
2026-01-30T14:08:29.823350Z info FLAG: --log_stacktrace_level="default:none"
2026-01-30T14:08:29.823356Z info FLAG: --log_target="[stdout]"
2026-01-30T14:08:29.823358Z info FLAG: --meshConfig="./etc/istio/config/mesh"
2026-01-30T14:08:29.823361Z info FLAG: --outlierLogPath=""
2026-01-30T14:08:29.823363Z info FLAG: --profiling="true"
2026-01-30T14:08:29.823365Z info FLAG: --proxyComponentLogLevel="misc:error"
2026-01-30T14:08:29.823368Z info FLAG: --proxyLogLevel="warning"
2026-01-30T14:08:29.823370Z info FLAG: --serviceCluster="istio-proxy"
2026-01-30T14:08:29.823372Z info FLAG: --stsPort="0"
2026-01-30T14:08:29.823375Z info FLAG: --templateFile=""
2026-01-30T14:08:29.823378Z info FLAG: --tokenManagerPlugin=""
2026-01-30T14:08:29.823387Z info FLAG: --vklog="0"
2026-01-30T14:08:29.823392Z info Version 1.23.6-6a112a28410654328342c68f82da48920e34f062-Clean
2026-01-30T14:08:29.823398Z info Set max file descriptors (ulimit -n) to: 1048576
2026-01-30T14:08:29.823719Z info Proxy role ips=[192.168.2.124] type=sidecar id=carts-88b4ddf98-p5gqh.demo domain=demo.svc.cluster.local
2026-01-30T14:08:29.823783Z info Apply proxy config from env {"discoveryAddress":"istiod-istio-helm.istio-helm-system.svc:15012","tracing":{"zipkin":{"address":"jaeger-jaeger-operator-jaeger-collector.istio-system.svc.cluster.local:9411"},"sampling":100}}
2026-01-30T14:08:29.825955Z info cpu limit detected as 2, setting concurrency
2026-01-30T14:08:29.827354Z info Effective config: binaryPath: /usr/local/bin/envoy
concurrency: 2
configPath: ./etc/istio/proxy
controlPlaneAuthPolicy: MUTUAL_TLS
discoveryAddress: istiod-istio-helm.istio-helm-system.svc:15012
drainDuration: 45s
proxyAdminPort: 15000
serviceCluster: istio-proxy
statNameLength: 189
statusPort: 15020
terminationDrainDuration: 5s
tracing:
sampling: 100
zipkin:
address: jaeger-jaeger-operator-jaeger-collector.istio-system.svc.cluster.local:9411
2026-01-30T14:08:29.827376Z info JWT policy is third-party-jwt
2026-01-30T14:08:29.827380Z info using credential fetcher of JWT type in cluster.local trust domain
2026-01-30T14:08:30.028797Z info Opening status port 15020
2026-01-30T14:08:30.028831Z info Starting default Istio SDS Server
2026-01-30T14:08:30.028850Z info CA Endpoint istiod-istio-helm.istio-helm-system.svc:15012, provider Citadel
2026-01-30T14:08:30.028879Z info Using CA istiod-istio-helm.istio-helm-system.svc:15012 cert with certs: var/run/secrets/istio/root-cert.pem
2026-01-30T14:08:30.029620Z info xdsproxy Initializing with upstream address "istiod-istio-helm.istio-helm-system.svc:15012" and cluster "Kubernetes"
2026-01-30T14:08:30.031212Z info Pilot SAN: [istiod-istio-helm.istio-helm-system.svc]
2026-01-30T14:08:30.033350Z info sds Starting SDS grpc server
2026-01-30T14:08:30.033374Z info sds Starting SDS server for workload certificates, will listen on "var/run/secrets/workload-spiffe-uds/socket"
2026-01-30T14:08:30.033503Z info starting Http service at 127.0.0.1:15004
2026-01-30T14:08:30.035641Z info Starting proxy agent
2026-01-30T14:08:30.035691Z info Envoy command: [-c etc/istio/proxy/envoy-rev.json --drain-time-s 45 --drain-strategy immediate --local-address-ip-version v4 --file-flush-interval-msec 1000 --disable-hot-restart --allow-unknown-static-fields -l warning --component-log-level misc:error --concurrency 2]
2026-01-30T14:08:30.112678Z warning envoy main external/envoy/source/server/server.cc:936 There is no configured limit to the number of allowed active downstream connections. Configure a limit in `envoy.resource_monitors.downstream_connections` resource monitor. thread=14
2026-01-30T14:08:30.114850Z warning envoy main external/envoy/source/server/server.cc:843 Usage of the deprecated runtime key overload.global_downstream_max_connections, consider switching to `envoy.resource_monitors.downstream_connections` instead.This runtime key will be removed in future. thread=14
2026-01-30T14:08:30.123541Z info xdsproxy connected to delta upstream XDS server: istiod-istio-helm.istio-helm-system.svc:15012 id=1
2026-01-30T14:08:30.156633Z info cache generated new workload certificate resourceName=default latency=126.712884ms ttl=23h59m59.843374146s
2026-01-30T14:08:30.156685Z info cache Root cert has changed, start rotating root cert
2026-01-30T14:08:30.156825Z info cache returned workload trust anchor from cache ttl=23h59m59.843176367s
2026-01-30T14:08:30.192919Z info ads ADS: new connection for node:1
2026-01-30T14:08:30.193043Z info cache returned workload certificate from cache ttl=23h59m59.806958673s
2026-01-30T14:08:30.193482Z info ads ADS: new connection for node:2
2026-01-30T14:08:30.193672Z info cache returned workload trust anchor from cache ttl=23h59m59.806329146s
2026-01-30T14:08:31.182561Z info Readiness succeeded in 1.360041593s
2026-01-30T14:08:31.183121Z info Envoy proxy is ready

เข้าใช้งาน application ผ่านทาง load balancer ip โดยหา load balancer ip ได้จาก cli

[nutanix@harbor ~]$ kubectl get svc front-end -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
front-end LoadBalancer 10.108.248.153 10.38.37.21 80:32763/TCP 9m19s

สามารถ monitor service ได้จาก kiali โดยไปที่ cluster แล้วเลือกเปิด kiali ตามภาพ

เลือก Traffic Graph และ namespace เป็น demo

หรือสามารถดู​ service tracing จาก Jaeger โดยเข้าจากหน้า cluster หลังจากเข้ามาที่หน้าจอหลัก เลือก service ที่ต้องการ

กดที่เหตุการที่สนใจ jaeger จะแสดงรายละเอียดของ process ดังรูปภาพ

Rook Ceph cluster

การใช้งาน rook ceph cluster ใน Nutanix Kubernetes Platform เพื่อให้บริการ s3 storage สำหรับ application โดย rook ceph ได้ถูกติดตั้งใน mode high availability มาให้ซึ่งต้องมีจำนวน worker node อย่างน้อย 3 worker node เพื่อให้ rook ceph cluster สามารถทำงานได้ การเปิดใช้งานให้เข้าไปที่ menu application และเลือก enable Rook Ceph และ Rook Ceph Cluster ตามลำดับ

ตรวจสอบว่า Rook Ceph ได้ติดตั้งเรียบร้อยแล้วจาก cli โดยเลือก namespace ตาม namespace ของ kubernetes cluster ที่เราติดตั้ง

[nutanix@harbor ~]$ kubectl get deployment -n kommander-default-workspace
NAME READY UP-TO-DATE AVAILABLE AGE
ceph-cosi-driver 1/1 1 1 4m29s
gatekeeper-audit 1/1 1 1 27h
gatekeeper-controller-manager 2/2 2 2 27h
kommander-default-workspace-reloader-reloader 1/1 1 1 27h
kommander-traefik 2/2 2 2 27h
kube-oidc-proxy 1/1 1 1 27h
rook-ceph-crashcollector-dev2-md-0-rm6kd-f4xbb-8bgsj 1/1 1 1 2m56s
rook-ceph-crashcollector-dev2-md-0-rm6kd-f4xbb-h9zqd 1/1 1 1 2m39s
rook-ceph-crashcollector-dev2-md-0-rm6kd-f4xbb-hrl4t 1/1 1 1 2m59s
rook-ceph-exporter-dev2-md-0-rm6kd-f4xbb-8bgsj 1/1 1 1 2m56s
rook-ceph-exporter-dev2-md-0-rm6kd-f4xbb-h9zqd 1/1 1 1 2m39s
rook-ceph-exporter-dev2-md-0-rm6kd-f4xbb-hrl4t 1/1 1 1 2m59s
rook-ceph-mgr-a 1/1 1 1 2m56s
rook-ceph-mgr-b 1/1 1 1 2m56s
rook-ceph-mon-a 1/1 1 1 4m53s
rook-ceph-mon-b 1/1 1 1 4m17s
rook-ceph-mon-c 1/1 1 1 3m39s
rook-ceph-operator 1/1 1 1 35m
rook-ceph-osd-0 1/1 1 1 117s
rook-ceph-osd-1 1/1 1 1 115s
rook-ceph-osd-2 1/1 1 1 116s
rook-ceph-osd-3 1/1 1 1 90s
rook-ceph-tools 1/1 1 1 5m29s
traefik-forward-auth 1/1 1 1 27h

เปิดหน้าจอ rook ceph clusterโดยเลือกจาก cluster

ใช้ default user คือ admin และใช้ cli เพื่อแสดงค่า default password

[nutanix@harbor ~]$ kubectl -n kommander-default-workspace get secret rook-ceph-dashboard-password -ojsonpath='{.data.password}' | base64 -d
6-$9>^PZj1^E!-*XKCdw

เมื่อ login เข้ามาแล้วจะได้หน้าจอดังนี้

เข้าไปที่เมนู Object และสร้าง user ใหม่

จากนั้นเลือก Bucket และ create new bucket

เข้าไปที่ Users อีกครั้งเพื่อ copy secret key และ access key สำหรับใช้ในการใช้งาน bucket ที่สร้างขึ้น

เลือก user และที่ Key section แล้วเลือกที่ username

กดปุ่ม show ก็จะแสดง username , Access Key และ Secret key สำหรับใช้ในการ access bucket

Create kubernetes work cluster from NKP CLI

นอกจากจะ create kubernetes ผ่านทาง GUI แล้วเรายังสามารถสร้าง kuberntes จาก nkp cli ได้ และข้อดีของการใช้ cli จะทำให้เราสามารถเลือก image สำหรับ worker node และ master node ที่ไม่อยู่ใน release ที่ NKP version provide ให้ เช่น nkp 2.17.0 จะมาพร้อมกับ kubernetes version 1.34.1 แต่ถ้าเราจะสร้าง kubernetes version 1.29.6 ก็จะไม่สามารถทำได้เนื่องจาก GUI ไม่มี drop down ให้เลือก ซึ่งกรณีนี้ต้องใช้ CLI ในการสร้าง

ก่อนที่จะใช้ CLI ต้องทำการ upload VM Image สำหรับ kuberntes ก่อน โดยตัวอย่างนี้ เรา download จาก Nutanix portal และได้ upload เข้าไปใน Prism Central

ใช้ CLI เพื่อสร้างไฟล์ metadata (yaml) สำหรับ create kubernetes cluster

Shell
nkp create cluster nutanix --cluster-name dev1 \
--namespace kommander-default-workspace \
--endpoint https://10.38.37.7:9440 \
--control-plane-prism-element-cluster PHX-POC265 \
--control-plane-subnets primary-PHX-POC265 \
--control-plane-replicas 1 \
--control-plane-endpoint-ip 10.38.37.14 \
--control-plane-vm-image nkp-rocky-9.4-release-1.29.6-20250609212843.qcow2 \
--worker-subnets primary-PHX-POC265 \
--worker-replicas=3 \
--worker-vm-image nkp-rocky-9.4-release-1.29.6-20250609212843.qcow2 \
--worker-prism-element-cluster PHX-POC265 \
--csi-storage-container default \
--kubernetes-service-load-balancer-ip-range 10.38.37.15-10.38.37.17 \
--registry-mirror-url https://10.38.37.51/nkp2120 \
--registry-mirror-username admin \
--registry-mirror-password Harbor12345 \
--registry-mirror-cacert /home/nutanix/harbor/certs/harbor.local.crt \
--ssh-public-key-file /home/nutanix/id_ed25519.pub \
--ssh-username nutanix \
--airgapped \
--insecure \
--verbose 5 \
--timeout 0 \
--dry-run \
--skip-preflight-checks=NutanixVMImageKubernetesVersion \
--output=yaml > dev1-cluster.yaml

ค่า parameter จะแตกต่างกันไปขึ้นอยู่กับ environment โดยตัวอย่างนี้ได้ทำการติดตั้ง harbor เป็น container registry สำหรับใช้ภายใน เนื่องจาก environment ไม่สามารถเชื่อต่อ internet ได้ ขั้นตอนการเตรียม harbor และ package bundle สามารถดูได้จากบทความก่อนหน้า

ทำการเปิดไฟล์ metadata ที่สร้างขึ้นและแก้ไข version ของ kubernetes ให้ตรงกับ version ของ vm image

สร้าง kubernetes cluster ด้วย cli

[nutanix@harbor ~]$ kubectl apply -f dev1-cluster.yaml
secret/dev1-pc-credentials created
secret/dev1-pc-credentials-for-csi created
secret/dev1-image-registry-mirror-credentials created
Warning: Cluster has skipped preflight check "NutanixVMImageKubernetesVersion"
cluster.cluster.x-k8s.io/dev1 created

ตรวจสอบสถานะการสร้าง cluster จาก GUI จะพบว่ามีการสร้าง kubernetes ตามที่ระบุใน spec ของ yaml file

Kubernetes node auto scaler

Nutanix Kubernetes Platform (NKP) สามารถกำหนดให้ Kubernetes worker node ทำการ scale ตัวเองโดยการเพิ่มจำนวน worker ได้อัตโนมัติเมื่อพบว่า cpu หรือ memory ไม่เพียงพอสำหรับการ start workload โดยสามารถเข้าไปที่ kubernetes cluster เลือก node pool และ edit worker ตามรูป

ตัวอย่างนี้กำหนด minimum ไว้ 3 node และ scale เพิ่มเป็น 5 node ถ้า resource ไม่เพียงพอสำหรับการ deploy workload ใหม่ ใน version ปัจจุบันยังเป็น autoscaling ตาม schedules ถ้า resource ไม่เพียงพอ ถ้าต้องการ Event driven autoscaling เช่น เมื่อ transaction เกิดค่าที่กำหนด โดยใช้ metric ที่ได้จาก prometheus ทำการ Trigger ให้ KEDA ทำการ scale POD ซึ่งเมื่อ resource ไม่เพียงพอ Node AutoScaler ก็จะทำงาน

หลังจาก set ค่า autoscaling แล้วตรวจสอบได้ด้วย kubectl cli

[nutanix@harbor ~]$ kubectl get cm cluster-autoscaler-status -n kube-system -o yaml
apiVersion: v1
data:
status: |
time: 2026-01-30 06:46:25.136551666 +0000 UTC
autoscalerStatus: Running
clusterWide:
health:
status: Healthy
nodeCounts:
registered:
total: 4
ready: 4
notStarted: 0
longUnregistered: 0
unregistered: 0
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
scaleUp:
status: NoActivity
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
scaleDown:
status: NoCandidates
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
nodeGroups:
- name: MachineDeployment/kommander-default-workspace/dev2-md-0-rm6kd
health:
status: Healthy
nodeCounts:
registered:
total: 3
ready: 3
notStarted: 0
longUnregistered: 0
unregistered: 0
cloudProviderTarget: 3
minSize: 3
maxSize: 5
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
scaleUp:
status: NoActivity
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
scaleDown:
status: NoCandidates
lastProbeTime: "2026-01-30T06:46:25.136551666Z"
lastTransitionTime: "2026-01-30T01:45:13.531234277Z"
kind: ConfigMap
metadata:
annotations:
cluster-autoscaler.kubernetes.io/last-updated: 2026-01-30 06:46:25.136551666 +0000
UTC
creationTimestamp: "2026-01-30T01:40:46Z"
name: cluster-autoscaler-status
namespace: kube-system
resourceVersion: "882305"
uid: 06b9b1e0-de1d-477b-b691-cbc172803f93

เพื่อทดสอบต้อง deploy workload ที่จอง resource มากกว่าที่ kubernetes จะสามารถจัดสรรให้ได้ ตามตัวอย่าง

[nutanix@harbor ~]$ more stress-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: resource-test
spec:
replicas: 10
selector:
matchLabels:
app: resource-test
template:
metadata:
labels:
app: resource-test
spec:
containers:
- name: stress-test
image: polinux/stress
resources:
requests:
memory: "1Gi"
cpu: "5000m"
limits:
memory: "2Gi"
cpu: "10000m"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "512M", "--timeout", "600s"]

ทำการ deploy ด้วย kubectl cli

[nutanix@harbor ~]$ kubectl apply -f stress-test.yaml

ตรวจสอบว่า workload สามารถ running ได้ แต่ไม่สามารถ running ได้ทั้งหมดเนื่องจาก resource ไม่พอ ระบบจะต้องทำการ scale node เพิ่ม

[nutanix@harbor ~]$ kubectl get pod
NAME READY STATUS RESTARTS AGE
resource-test-655798b6c9-6j852 0/1 Pending 0 34s
resource-test-655798b6c9-7c8np 0/1 Pending 0 34s
resource-test-655798b6c9-7rj8x 0/1 Pending 0 34s
resource-test-655798b6c9-7vwvr 0/1 Pending 0 34s
resource-test-655798b6c9-fbk9f 1/1 Running 0 34s
resource-test-655798b6c9-gj8wk 0/1 Pending 0 34s
resource-test-655798b6c9-m9clc 1/1 Running 0 34s
resource-test-655798b6c9-qc8sn 1/1 Running 0 34s
resource-test-655798b6c9-vhbdh 0/1 Pending 0 34s
resource-test-655798b6c9-xd8nf 0/1 Pending 0 34s

ตรวจสอบจำนวน node ปัจจุบัน

[nutanix@harbor ~]$ kubectl get node
NAME STATUS ROLES AGE VERSION
dev2-hh42l-kc65c Ready control-plane 5h5m v1.34.1
dev2-md-0-rm6kd-f4xbb-8bgsj Ready <none> 4h58m v1.34.1
dev2-md-0-rm6kd-f4xbb-h9zqd Ready <none> 5h2m v1.34.1
dev2-md-0-rm6kd-f4xbb-hrl4t Ready <none> 5h v1.34.1

ตรวจสอบอีกครั้งเพื่อดูจำนวนว่า node ได้เพิ่มขึ้นเป็น 5 node ตามที่กำหนดค่าไว้

[nutanix@harbor ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
dev2-hh42l-kc65c Ready control-plane 5h8m v1.34.1
dev2-md-0-rm6kd-f4xbb-8bgsj Ready <none> 5h1m v1.34.1
dev2-md-0-rm6kd-f4xbb-cp7z4 Ready <none> 98s v1.34.1
dev2-md-0-rm6kd-f4xbb-h9zqd Ready <none> 5h5m v1.34.1
dev2-md-0-rm6kd-f4xbb-hrl4t Ready <none> 5h3m v1.34.1
dev2-md-0-rm6kd-f4xbb-lbx84 Ready <none> 101s v1.34.1

การ auto ลดลงเมื่อ resource กลับมาสู่ภาวะปกติ จะมีหลายปัจจัยเช่น resource โดยรวมจะต้องน้อยกว่า 50% เป็นต้น ข้อมูลเพิ่มเติมสามารถศึกษาเพิ่มเติมได้จาก Kubernetes Node Autoscaler

Bring your own app to NKP App Catalog

Nutanix Kubernetes Platform สามารถที่จะนำเข้า kubernetes application package ทั้งที่เป็น helm chart หรือ kustomization โดยสามารถดูรายละเอียดการสร้าง git project ได้จาก https://github.com/pkhamdee/nkp-demo-catalog-applications การสร้าง project ต้องสร้าง folder services และมี list ของ service ที่ต้องการให้แสดงที่ app catalog ตามโครงสร้างดังนี้

ภายใน folder service ต้องสร้างไฟล์ metadata.yaml ที่มีรายละเอียดของ application ตามตัวอย่าง โดยข้อมูลนี้จะแสดงในหน้า catalog

YAML
displayName: Anything LLM
description: all-in-one AI application with built-in RAG, AI agents, and more.
category:
- AI
type: catalog
allowMultipleInstances: false
scope:
- workspace
licensing:
- Ultimate
- Enterprise
certifications:
overview: |-
## Product Overview
AnythingLLM is a full-stack application where you can use commercial off-the-shelf LLMs or popular open source LLMs and vectorDB solutions to build a private ChatGPT with no compromises that you can run locally as well as host remotely and be able to chat intelligently with any documents you provide it.
AnythingLLM divides your documents into objects called `workspaces`. A Workspace functions a lot like a thread, but with the addition of containerization of your documents. Workspaces can share documents, but they do not talk to each other so you can keep your context for each workspace clean.
### Cool features of AnythingLLM
- 🆕 [**Custom AI Agents**](https://docs.anythingllm.com/agent/custom/introduction)
- 🖼️ **Multi-modal support (both closed and open-source LLMs!)**
- 👤 Multi-user instance support and permissioning _Docker version only_
- 🦾 Agents inside your workspace (browse the web, run code, etc)
- 💬 [Custom Embeddable Chat widget for your website](./embed/README.md) _Docker version only_
- 📖 Multiple document type support (PDF, TXT, DOCX, etc)
- Simple chat UI with Drag-n-Drop funcitonality and clear citations.
- 100% Cloud deployment ready.
- Works with all popular [closed and open-source LLM providers](#supported-llms-embedder-models-speech-models-and-vector-databases).
- Built-in cost & time-saving measures for managing very large documents compared to any other chat UI.
- Full Developer API for custom integrations!
- Much more...install and find out!
icon: PHN2ZyB3aWR0aD0iOTMiIGhlaWdodD0iOTMiIHZpZXdCb3g9IjAgMCA5MyA5MyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF80NzAzXzEwMTg4KSI+CjxyZWN0IHg9IjQiIHdpZHRoPSI4NC4xODc1IiBoZWlnaHQ9Ijg0LjE4NzUiIHJ4PSIyMCIgZmlsbD0iIzFCMUIxRSIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTUwLjgyNjkgNTAuNDA5N0M1MC4yOTY0IDUxLjA5NSA1MC4yODM0IDUyLjA1NjYgNTAuODE2OCA1Mi43NTFMNTUuMjUzNSA1OC45MTU1TDU1LjI2ODMgNTguOTM0MkM1Ni4yNDU3IDYwLjE2NDQgNTcuNzE5NSA2MC44NzAxIDU5LjI4OTggNjAuODcwMUg2OS4yMDNDNzIuMDEzOCA2MC44NzAxIDc0LjMxMSA1OC42MTA5IDc0LjMxMSA1NS44MDlWMjkuNzg0OEM3NC4zMTEgMjYuOTg2OCA3Mi4wMTQzIDI0LjcyMzYgNjkuMjAzIDI0LjcyMzZINTkuMjg5OEM1Ny43MTUzIDI0LjcyMzYgNTYuMjQ0MyAyNS40MzAzIDU1LjI2NyAyNi42NjQ2TDQ2LjI1NzkgMzguMDUwMkwzMi42MzcxIDU1LjI2MUgyMy41MjU4VjMwLjMzNjJIMzIuNjE5N0wzNy41Nzc5IDM3LjAzNTRMMzcuNTg3NyAzNy4wNDc3QzM4LjM2MDUgMzguMDI2IDM5Ljg0MzMgMzguMDIwMSA0MC42MTU5IDM3LjA1MTNMNDEuNzYxNCAzNS42MDMyTDQxLjc2NzggMzUuNTk0OUM0Mi4yOTkzIDM0LjkwODQgNDIuMzEyNCAzMy45NDIzIDQxLjc2ODkgMzMuMjQ2M0wzNi45Mjk2IDI2LjY3NzlMMzYuOTE4OCAyNi42NjQzQzM1Ljk0MTggMjUuNDMwNCAzNC40NzQ3IDI0LjcyMzYgMzIuODk2MiAyNC43MjM2SDIyLjk4M0MyMC4xODMyIDI0LjcyMzYgMTcuODc1IDI2Ljk4OTEgMTcuODc1IDI5Ljc4NDhWNTUuODEyNEMxNy44NzUgNTguNjA3NSAyMC4xNzkyIDYwLjg3MzUgMjIuOTgzIDYwLjg3MzVIMzIuODk2MkMzNC40NzQ0IDYwLjg3MzUgMzUuOTQwOSA2MC4xNjcxIDM2LjkxNzcgNTguOTM3Nkw1OS41NTE3IDMwLjMzMjhINjguNjYwMlY1NS4yNTc1SDU5LjU2NDZMNTUuMDIyOSA0OC45NjlMNTUuMDA4MyA0OC45NTA3QzU0LjIzNzMgNDcuOTgyIDUyLjc1NjEgNDcuOTgyIDUxLjk4NTIgNDguOTUwN0w1MC44MzQ0IDUwLjQwMDFMNTAuODI2OSA1MC40MDk3Wk0zMy4yNTY0IDU2LjA4OThMNDAuMTE4MSA0Ny40MTk2TDQ3LjA0MjEgMzguNjcwN0w1Ni4wNTEyIDI3LjI4NTFMMzMuMjU2NCA1Ni4wODk4WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzQ3MDNfMTAxODgpIi8+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIxX2JpXzQ3MDNfMTAxODgpIj4KPG1hc2sgaWQ9InBhdGgtMy1vdXRzaWRlLTFfNDcwM18xMDE4OCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMTQuODc1IiB5PSIyMS43MjM2IiB3aWR0aD0iNjMiIGhlaWdodD0iNDMiIGZpbGw9ImJsYWNrIj4KPHJlY3QgZmlsbD0id2hpdGUiIHg9IjE0Ljg3NSIgeT0iMjEuNzIzNiIgd2lkdGg9IjYzIiBoZWlnaHQ9IjQzIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNTAuODI2OSA1MC40MDk3QzUwLjI5NjQgNTEuMDk1IDUwLjI4MzQgNTIuMDU2NiA1MC44MTY4IDUyLjc1MUw1NS4yNTM1IDU4LjkxNTVMNTUuMjY4MyA1OC45MzQyQzU2LjI0NTcgNjAuMTY0NCA1Ny43MTk1IDYwLjg3MDEgNTkuMjg5OCA2MC44NzAxSDY5LjIwM0M3Mi4wMTM4IDYwLjg3MDEgNzQuMzExIDU4LjYxMDkgNzQuMzExIDU1LjgwOVYyOS43ODQ4Qzc0LjMxMSAyNi45ODY4IDcyLjAxNDMgMjQuNzIzNiA2OS4yMDMgMjQuNzIzNkg1OS4yODk4QzU3LjcxNTMgMjQuNzIzNiA1Ni4yNDQzIDI1LjQzMDMgNTUuMjY3IDI2LjY2NDZMNDYuMjU3OSAzOC4wNTAyTDMyLjYzNzEgNTUuMjYxSDIzLjUyNThWMzAuMzM2MkgzMi42MTk3TDM3LjU3NzkgMzcuMDM1NEwzNy41ODc3IDM3LjA0NzdDMzguMzYwNSAzOC4wMjYgMzkuODQzMyAzOC4wMjAxIDQwLjYxNTkgMzcuMDUxM0w0MS43NjE0IDM1LjYwMzJMNDEuNzY3OCAzNS41OTQ5QzQyLjI5OTMgMzQuOTA4NCA0Mi4zMTI0IDMzLjk0MjMgNDEuNzY4OSAzMy4yNDYzTDM2LjkyOTYgMjYuNjc3OUwzNi45MTg4IDI2LjY2NDNDMzUuOTQxOCAyNS40MzA0IDM0LjQ3NDcgMjQuNzIzNiAzMi44OTYyIDI0LjcyMzZIMjIuOTgzQzIwLjE4MzIgMjQuNzIzNiAxNy44NzUgMjYuOTg5MSAxNy44NzUgMjkuNzg0OFY1NS44MTI0QzE3Ljg3NSA1OC42MDc1IDIwLjE3OTIgNjAuODczNSAyMi45ODMgNjAuODczNUgzMi44OTYyQzM0LjQ3NDQgNjAuODczNSAzNS45NDA5IDYwLjE2NzEgMzYuOTE3NyA1OC45Mzc2TDU5LjU1MTcgMzAuMzMyOEg2OC42NjAyVjU1LjI1NzVINTkuNTY0Nkw1NS4wMjI5IDQ4Ljk2OUw1NS4wMDgzIDQ4Ljk1MDdDNTQuMjM3MyA0Ny45ODIgNTIuNzU2MSA0Ny45ODIgNTEuOTg1MiA0OC45NTA3TDUwLjgzNDQgNTAuNDAwMUw1MC44MjY5IDUwLjQwOTdaTTMzLjI1NjQgNTYuMDg5OEw0MC4xMTgxIDQ3LjQxOTZMNDcuMDQyMSAzOC42NzA3TDU2LjA1MTIgMjcuMjg1MUwzMy4yNTY0IDU2LjA4OThaIi8+CjwvbWFzaz4KPHBhdGggZD0iTTUwLjgxNjggNTIuNzUxTDUzLjI1MTcgNTAuOTk4NUw1My4yMjQ0IDUwLjk2MDVMNTMuMTk1OSA1MC45MjM0TDUwLjgxNjggNTIuNzUxWk01MC44MjY5IDUwLjQwOTdMNTMuMTk5MiA1Mi4yNDYyTDUzLjE5OTUgNTIuMjQ1OEw1MC44MjY5IDUwLjQwOTdaTTU1LjI1MzUgNTguOTE1NUw1Mi44MTg2IDYwLjY2OEw1Mi44NjAzIDYwLjcyNjFMNTIuOTA0OCA2MC43ODJMNTUuMjUzNSA1OC45MTU1Wk01NS4yNjgzIDU4LjkzNDJMNTcuNjE3MiA1Ny4wNjc5TDU3LjYxNjkgNTcuMDY3N0w1NS4yNjgzIDU4LjkzNDJaTTU1LjI2NyAyNi42NjQ2TDUyLjkxNSAyNC44MDIzTDUyLjkxNDUgMjQuODAzTDU1LjI2NyAyNi42NjQ2Wk00Ni4yNTc5IDM4LjA1MDJMNDguNjEwMyAzOS45MTE5TDQ4LjYxMDUgMzkuOTExN0w0Ni4yNTc5IDM4LjA1MDJaTTMyLjYzNzEgNTUuMjYxVjU4LjI2MUgzNC4wODg2TDM0Ljk4OTUgNTcuMTIyN0wzMi42MzcxIDU1LjI2MVpNMjMuNTI1OCA1NS4yNjFIMjAuNTI1OFY1OC4yNjFIMjMuNTI1OFY1NS4yNjFaTTIzLjUyNTggMzAuMzM2MlYyNy4zMzYySDIwLjUyNThWMzAuMzM2MkgyMy41MjU4Wk0zMi42MTk3IDMwLjMzNjJMMzUuMDMxMSAyOC41NTE1TDM0LjEzMTcgMjcuMzM2MkgzMi42MTk3VjMwLjMzNjJaTTM3LjU3NzkgMzcuMDM1NEwzNS4xNjY1IDM4LjgyMDFMMzUuMTk0NiAzOC44NThMMzUuMjIzOSAzOC44OTVMMzcuNTc3OSAzNy4wMzU0Wk0zNy41ODc3IDM3LjA0NzdMMzUuMjMzNiAzOC45MDc0TDM1LjIzMzcgMzguOTA3NEwzNy41ODc3IDM3LjA0NzdaTTQwLjYxNTkgMzcuMDUxM0w0Mi45NjE1IDM4LjkyMTdMNDIuOTY4NyAzOC45MTI1TDQwLjYxNTkgMzcuMDUxM1pNNDEuNzYxNCAzNS42MDMyTDQ0LjExNDIgMzcuNDY0NEw0NC4xMjM5IDM3LjQ1MjJMNDQuMTMzNSAzNy40Mzk4TDQxLjc2MTQgMzUuNjAzMlpNNDEuNzY3OCAzNS41OTQ5TDQ0LjEzOTkgMzcuNDMxNkw0NC4xNDAxIDM3LjQzMTNMNDEuNzY3OCAzNS41OTQ5Wk00MS43Njg5IDMzLjI0NjNMMzkuMzUzNiAzNS4wMjU4TDM5LjM3ODYgMzUuMDU5NkwzOS40MDQ0IDM1LjA5MjdMNDEuNzY4OSAzMy4yNDYzWk0zNi45Mjk2IDI2LjY3NzlMMzkuMzQ0OCAyNC44OTg1TDM5LjMxMzkgMjQuODU2NEwzOS4yODE0IDI0LjgxNTVMMzYuOTI5NiAyNi42Nzc5Wk0zNi45MTg4IDI2LjY2NDNMMzQuNTY2OCAyOC41MjY2TDM0LjU2NjkgMjguNTI2OEwzNi45MTg4IDI2LjY2NDNaTTM2LjkxNzcgNTguOTM3NkwzOS4yNjY2IDYwLjgwMzhMMzkuMjcwMyA2MC43OTkxTDM2LjkxNzcgNTguOTM3NlpNNTkuNTUxNyAzMC4zMzI4VjI3LjMzMjhINTguMDk5OUw1Ny4xOTkxIDI4LjQ3MTNMNTkuNTUxNyAzMC4zMzI4Wk02OC42NjAyIDMwLjMzMjhINzEuNjYwMlYyNy4zMzI4SDY4LjY2MDJWMzAuMzMyOFpNNjguNjYwMiA1NS4yNTc1VjU4LjI1NzVINzEuNjYwMlY1NS4yNTc1SDY4LjY2MDJaTTU5LjU2NDYgNTUuMjU3NUw1Ny4xMzI1IDU3LjAxNEw1OC4wMzA2IDU4LjI1NzVINTkuNTY0NlY1NS4yNTc1Wk01NS4wMjI5IDQ4Ljk2OUw1Ny40NTQ5IDQ3LjIxMjVMNTcuNDEzOCA0Ny4xNTU3TDU3LjM3MDEgNDcuMTAwOEw1NS4wMjI5IDQ4Ljk2OVpNNTUuMDA4MyA0OC45NTA3TDUyLjY2MDkgNTAuODE4OUw1Mi42NjEgNTAuODE5TDU1LjAwODMgNDguOTUwN1pNNTEuOTg1MiA0OC45NTA3TDQ5LjYzNzggNDcuMDgyNkw0OS42MzU3IDQ3LjA4NTNMNTEuOTg1MiA0OC45NTA3Wk01MC44MzQ0IDUwLjQwMDFMNDguNDg0OSA0OC41MzQ2TDQ4LjQ3MzMgNDguNTQ5Mkw0OC40NjE5IDQ4LjU2NEw1MC44MzQ0IDUwLjQwMDFaTTMzLjI1NjQgNTYuMDg5OEwzMC45MDM5IDU0LjIyODFMMzUuNjA4OCA1Ny45NTE1TDMzLjI1NjQgNTYuMDg5OFpNNDAuMTE4MSA0Ny40MTk2TDM3Ljc2NTYgNDUuNTU3OUwzNy43NjU2IDQ1LjU1NzlMNDAuMTE4MSA0Ny40MTk2Wk00Ny4wNDIxIDM4LjY3MDdMNDkuMzk0NSA0MC41MzI0TDQ5LjM5NDcgNDAuNTMyMkw0Ny4wNDIxIDM4LjY3MDdaTTU2LjA1MTIgMjcuMjg1MUw1OC40MDM4IDI5LjE0NjZMNTMuNjk4OCAyNS40MjM0TDU2LjA1MTIgMjcuMjg1MVpNNTMuMTk1OSA1MC45MjM0QzUzLjUwODcgNTEuMzMwNyA1My40ODcxIDUxLjg3NDIgNTMuMTk5MiA1Mi4yNDYyTDQ4LjQ1NDcgNDguNTczM0M0Ny4xMDU3IDUwLjMxNTkgNDcuMDU4MSA1Mi43ODI1IDQ4LjQzNzcgNTQuNTc4NUw1My4xOTU5IDUwLjkyMzRaTTU3LjY4ODQgNTcuMTYzMUw1My4yNTE3IDUwLjk5ODVMNDguMzgxOCA1NC41MDM0TDUyLjgxODYgNjAuNjY4TDU3LjY4ODQgNTcuMTYzMVpNNTcuNjE2OSA1Ny4wNjc3TDU3LjYwMjEgNTcuMDQ5TDUyLjkwNDggNjAuNzgyTDUyLjkxOTYgNjAuODAwN0w1Ny42MTY5IDU3LjA2NzdaTTU5LjI4OTggNTcuODcwMUM1OC42MjczIDU3Ljg3MDEgNTguMDIxIDU3LjU3NjMgNTcuNjE3MiA1Ny4wNjc5TDUyLjkxOTQgNjAuODAwNEM1NC40NzA0IDYyLjc1MjQgNTYuODExNyA2My44NzAxIDU5LjI4OTggNjMuODcwMVY1Ny44NzAxWk02OS4yMDMgNTcuODcwMUg1OS4yODk4VjYzLjg3MDFINjkuMjAzVjU3Ljg3MDFaTTcxLjMxMSA1NS44MDlDNzEuMzExIDU2LjkyMTYgNzAuMzg5NiA1Ny44NzAxIDY5LjIwMyA1Ny44NzAxVjYzLjg3MDFDNzMuNjM3OSA2My44NzAxIDc3LjMxMSA2MC4zMDAzIDc3LjMxMSA1NS44MDlINzEuMzExWk03MS4zMTEgMjkuNzg0OFY1NS44MDlINzcuMzExVjI5Ljc4NDhINzEuMzExWk02OS4yMDMgMjcuNzIzNkM3MC4zODg0IDI3LjcyMzYgNzEuMzExIDI4LjY3NDQgNzEuMzExIDI5Ljc4NDhINzcuMzExQzc3LjMxMSAyNS4yOTkxIDczLjY0MDIgMjEuNzIzNiA2OS4yMDMgMjEuNzIzNlYyNy43MjM2Wk01OS4yODk4IDI3LjcyMzZINjkuMjAzVjIxLjcyMzZINTkuMjg5OFYyNy43MjM2Wk01Ny42MTkgMjguNTI2OUM1OC4wMjMzIDI4LjAxNjMgNTguNjI1NCAyNy43MjM2IDU5LjI4OTggMjcuNzIzNlYyMS43MjM2QzU2LjgwNTEgMjEuNzIzNiA1NC40NjU0IDIyLjg0NDMgNTIuOTE1IDI0LjgwMjNMNTcuNjE5IDI4LjUyNjlaTTQ4LjYxMDUgMzkuOTExN0w1Ny42MTk2IDI4LjUyNjFMNTIuOTE0NSAyNC44MDNMNDMuOTA1MyAzNi4xODg2TDQ4LjYxMDUgMzkuOTExN1pNMzQuOTg5NSA1Ny4xMjI3TDQ4LjYxMDMgMzkuOTExOUw0My45MDU0IDM2LjE4ODRMMzAuMjg0NiA1My4zOTkyTDM0Ljk4OTUgNTcuMTIyN1pNMjMuNTI1OCA1OC4yNjFIMzIuNjM3MVY1Mi4yNjFIMjMuNTI1OFY1OC4yNjFaTTIwLjUyNTggMzAuMzM2MlY1NS4yNjFIMjYuNTI1OFYzMC4zMzYySDIwLjUyNThaTTMyLjYxOTcgMjcuMzM2MkgyMy41MjU4VjMzLjMzNjJIMzIuNjE5N1YyNy4zMzYyWk0zOS45ODkzIDM1LjI1MDZMMzUuMDMxMSAyOC41NTE1TDMwLjIwODMgMzIuMTIwOUwzNS4xNjY1IDM4LjgyMDFMMzkuOTg5MyAzNS4yNTA2Wk0zOS45NDE3IDM1LjE4OEwzOS45MzIgMzUuMTc1N0wzNS4yMjM5IDM4Ljg5NUwzNS4yMzM2IDM4LjkwNzRMMzkuOTQxNyAzNS4xODhaTTM4LjI3MDQgMzUuMTgwOUMzOC42OTExIDM0LjY1MzIgMzkuNTA1OCAzNC42MzYyIDM5Ljk0MTcgMzUuMTg4TDM1LjIzMzcgMzguOTA3NEMzNy4yMTUzIDQxLjQxNTcgNDAuOTk1NiA0MS4zODcgNDIuOTYxNSAzOC45MjE3TDM4LjI3MDQgMzUuMTgwOVpNMzkuNDA4NiAzMy43NDJMMzguMjYzMSAzNS4xOUw0Mi45Njg3IDM4LjkxMjVMNDQuMTE0MiAzNy40NjQ0TDM5LjQwODYgMzMuNzQyWk0zOS4zOTU4IDMzLjc1ODNMMzkuMzg5MyAzMy43NjY1TDQ0LjEzMzUgMzcuNDM5OEw0NC4xMzk5IDM3LjQzMTZMMzkuMzk1OCAzMy43NTgzWk0zOS40MDQ0IDM1LjA5MjdDMzkuMDgzNiAzNC42ODE5IDM5LjEwNyAzNC4xMzEzIDM5LjM5NTYgMzMuNzU4NUw0NC4xNDAxIDM3LjQzMTNDNDUuNDkxNiAzNS42ODU1IDQ1LjU0MTEgMzMuMjAyNyA0NC4xMzM0IDMxLjRMMzkuNDA0NCAzNS4wOTI3Wk0zNC41MTQzIDI4LjQ1NzRMMzkuMzUzNiAzNS4wMjU4TDQ0LjE4NDIgMzEuNDY2OEwzOS4zNDQ4IDI0Ljg5ODVMMzQuNTE0MyAyOC40NTc0Wk0zNC41NjY5IDI4LjUyNjhMMzQuNTc3NyAyOC41NDA0TDM5LjI4MTQgMjQuODE1NUwzOS4yNzA3IDI0LjgwMTlMMzQuNTY2OSAyOC41MjY4Wk0zMi44OTYyIDI3LjcyMzZDMzMuNTYzMyAyNy43MjM2IDM0LjE2MjEgMjguMDE1NSAzNC41NjY4IDI4LjUyNjZMMzkuMjcwOCAyNC44MDJDMzcuNzIxNSAyMi44NDUzIDM1LjM4NiAyMS43MjM2IDMyLjg5NjIgMjEuNzIzNlYyNy43MjM2Wk0yMi45ODMgMjcuNzIzNkgzMi44OTYyVjIxLjcyMzZIMjIuOTgzVjI3LjcyMzZaTTIwLjg3NSAyOS43ODQ4QzIwLjg3NSAyOC42ODAyIDIxLjgwNTYgMjcuNzIzNiAyMi45ODMgMjcuNzIzNlYyMS43MjM2QzE4LjU2MDggMjEuNzIzNiAxNC44NzUgMjUuMjk4IDE0Ljg3NSAyOS43ODQ4SDIwLjg3NVpNMjAuODc1IDU1LjgxMjRWMjkuNzg0OEgxNC44NzVWNTUuODEyNEgyMC44NzVaTTIyLjk4MyA1Ny44NzM1QzIxLjgwMzMgNTcuODczNSAyMC44NzUgNTYuOTE4MSAyMC44NzUgNTUuODEyNEgxNC44NzVDMTQuODc1IDYwLjI5NjkgMTguNTU1IDYzLjg3MzUgMjIuOTgzIDYzLjg3MzVWNTcuODczNVpNMzIuODk2MiA1Ny44NzM1SDIyLjk4M1Y2My44NzM1SDMyLjg5NjJWNTcuODczNVpNMzQuNTY4OSA1Ny4wNzEzQzM0LjE2MzggNTcuNTgxMSAzMy41NjM4IDU3Ljg3MzUgMzIuODk2MiA1Ny44NzM1VjYzLjg3MzVDMzUuMzg0OSA2My44NzM1IDM3LjcxNzkgNjIuNzUzIDM5LjI2NjYgNjAuODAzOEwzNC41Njg5IDU3LjA3MTNaTTU3LjE5OTEgMjguNDcxM0wzNC41NjUxIDU3LjA3NjFMMzkuMjcwMyA2MC43OTkxTDYxLjkwNDMgMzIuMTk0M0w1Ny4xOTkxIDI4LjQ3MTNaTTY4LjY2MDIgMjcuMzMyOEg1OS41NTE3VjMzLjMzMjhINjguNjYwMlYyNy4zMzI4Wk03MS42NjAyIDU1LjI1NzVWMzAuMzMyOEg2NS42NjAyVjU1LjI1NzVINzEuNjYwMlpNNTkuNTY0NiA1OC4yNTc1SDY4LjY2MDJWNTIuMjU3NUg1OS41NjQ2VjU4LjI1NzVaTTUyLjU5MDggNTAuNzI1NUw1Ny4xMzI1IDU3LjAxNEw2MS45OTY2IDUzLjUwMTFMNTcuNDU0OSA0Ny4yMTI1TDUyLjU5MDggNTAuNzI1NVpNNTIuNjYxIDUwLjgxOUw1Mi42NzU2IDUwLjgzNzJMNTcuMzcwMSA0Ny4xMDA4TDU3LjM1NTYgNDcuMDgyNUw1Mi42NjEgNTAuODE5Wk01NC4zMzI1IDUwLjgxODlDNTMuOTAyNCA1MS4zNTkzIDUzLjA5MSA1MS4zNTkzIDUyLjY2MDkgNTAuODE4OUw1Ny4zNTU3IDQ3LjA4MjZDNTUuMzgzNiA0NC42MDQ3IDUxLjYwOTggNDQuNjA0NyA0OS42Mzc4IDQ3LjA4MjZMNTQuMzMyNSA1MC44MTg5Wk01My4xODM5IDUyLjI2NTVMNTQuMzM0NiA1MC44MTYyTDQ5LjYzNTcgNDcuMDg1M0w0OC40ODQ5IDQ4LjUzNDZMNTMuMTgzOSA1Mi4yNjU1Wk01My4xOTk1IDUyLjI0NThMNTMuMjA3IDUyLjIzNjFMNDguNDYxOSA0OC41NjRMNDguNDU0NCA0OC41NzM3TDUzLjE5OTUgNTIuMjQ1OFpNMzUuNjA4OCA1Ny45NTE1TDQyLjQ3MDUgNDkuMjgxM0wzNy43NjU2IDQ1LjU1NzlMMzAuOTA0IDU0LjIyOEwzNS42MDg4IDU3Ljk1MTVaTTQyLjQ3MDUgNDkuMjgxM0w0OS4zOTQ1IDQwLjUzMjRMNDQuNjg5NiAzNi44MDg5TDM3Ljc2NTYgNDUuNTU3OUw0Mi40NzA1IDQ5LjI4MTNaTTQ5LjM5NDcgNDAuNTMyMkw1OC40MDM4IDI5LjE0NjZMNTMuNjk4NyAyNS40MjM1TDQ0LjY4OTUgMzYuODA5MUw0OS4zOTQ3IDQwLjUzMjJaTTUzLjY5ODggMjUuNDIzNEwzMC45MDM5IDU0LjIyODFMMzUuNjA4OSA1Ny45NTE0TDU4LjQwMzcgMjkuMTQ2N0w1My42OTg4IDI1LjQyMzRaIiBmaWxsPSJ1cmwoI3BhaW50MV9saW5lYXJfNDcwM18xMDE4OCkiIGZpbGwtb3BhY2l0eT0iMC4xIiBtYXNrPSJ1cmwoI3BhdGgtMy1vdXRzaWRlLTFfNDcwM18xMDE4OCkiLz4KPC9nPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfNDcwM18xMDE4OCIgeD0iMCIgeT0iMCIgd2lkdGg9IjkyLjE4NzUiIGhlaWdodD0iOTIuMTg3NSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz4KPGZlT2Zmc2V0IGR5PSI0Ii8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjI1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDcwM18xMDE4OCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NzAzXzEwMTg4IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIxX2JpXzQ3MDNfMTAxODgiIHg9IjEyLjIyNTUiIHk9IjE5LjA3NDEiIHdpZHRoPSI2Ny43MzY1IiBoZWlnaHQ9IjQ3LjQ0ODkiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlR2F1c3NpYW5CbHVyIGluPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHN0ZERldmlhdGlvbj0iMS4zMjQ3NSIvPgo8ZmVDb21wb3NpdGUgaW4yPSJTb3VyY2VBbHBoYSIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImVmZmVjdDFfYmFja2dyb3VuZEJsdXJfNDcwM18xMDE4OCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfYmFja2dyb3VuZEJsdXJfNDcwM18xMDE4OCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz4KPGZlT2Zmc2V0IGR5PSIwLjQ0MTU4NCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIwLjY2MjM3NiIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJhcml0aG1ldGljIiBrMj0iLTEiIGszPSIxIi8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDEgMCAwIDAgMCAxIDAgMCAwIDAgMSAwIDAgMCAwLjE1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9InNoYXBlIiByZXN1bHQ9ImVmZmVjdDJfaW5uZXJTaGFkb3dfNDcwM18xMDE4OCIvPgo8L2ZpbHRlcj4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzQ3MDNfMTAxODgiIHgxPSIxOC44NzUiIHkxPSIyNS43MjM2IiB4Mj0iNzcuNTI1IiB5Mj0iNDkuNDYyNCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNzVBNUZGIi8+CjxzdG9wIG9mZnNldD0iMC43MDMxMjUiIHN0b3AtY29sb3I9IiMyM0U1RkYiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzQ3MDNfMTAxODgiIHgxPSIxOS42Mzg2IiB5MT0iMjYuMzY2OCIgeDI9Ijc0LjMxMSIgeTI9IjI2LjM2NjgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzNDREVCNiIvPgo8c3RvcCBvZmZzZXQ9IjAuNjU2MjUiIHN0b3AtY29sb3I9IiMzNjRBRkYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K

ภายใน folder service สร้าง folder version และสร้าง file kustomization.yaml เพื่อให้เชื่อมไปยัง kubernetes application package ที่ต้องการ deploy เมื่อมีการ enable app ผ่าน nkp app catalog

ตัวอย่าง file kustomization.yaml

YAML
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrelease.yaml
- ../../../helm-repositories/la-cc.github.io/la-cc.github.io

ใช้ nkp cli เพื่อนำเข้า app catalog ตามตัวอย่าง

Shell
nkp create catalog nkp-demo-catalog-applications \
-w kommander-workspace \
--branch main \
--url https://github.com/pkhamdee/nkp-demo-catalog-applications

หลังจากนั้นตรวจสอบ application ที่แสดงใน workspace ตามที่ระบุใน cli ทั้งนี้เราสามารถเลือกได้ว่าจะให้ app แสดงใน workspace ไหนได้ใน cli

Install NDK with NKP

Nutanix Data Service for Kubernetes (NDK) เป็น service ที่ติดตั้งบน Kubernetes Cluster เพื่อทำการ backup application และ replicate ข้อมูลที่ถูก backup ไปยัง Kubernetes cluster ปลายทาง เพื่อให้สามารถ recovery ทั้ง application และข้อมูลได้ โดยใช้กับ use case เช่นการทำ DR โดยการ replicate ข้อมูลไปยังปลายทางมากกว่าหนึ่งปลายทาง การย้าย Application (Application migration) หรือ rebalancing application ไปยัง cluster ที่มี resource ที่เหมาะสม การ snapshots และ restore ภายใน cluster โดยสามารสร้าง schedule และรองรับการ replication ในแบบ near realtime ได้

CRD ที่มาด้วยกับ NDK

Application สำหรับกำหนดกลุ่มของ resource ที่จะทำการ snapshot หรือ replicate
ApplicationSnapshot สร้าง application snapshot สำหรับ source ที่ระบุ (application)
Remote จับคู่ของ NKP instance ระหว่างสอง cluster
ReplicationTarget กำหนด cluster และ namespace ของ cluster ปลายทาง
ApplicationSnapshotReplication ทำการ replicate snapshot ระหร่วง cluster
ApplicationSnapshotRestore ทำการ restore snapshot ที่ local cluster หรือ remote cluster
JobScheduler, ProtectionPlan กำหนด snapshot policy และการจัดเก็บ (retention)
StorageCluster กำหนด Infrastructure ที่จะ support การทำ snapshot และ replication ระหว่าง cluster หรือภายใน cluster

การติดตั้ง NDK โดยต้องติดตั้งทั้ง Kuberntes Cluster ต้นทาง และ Kubernetes Cluster ปลายทาง

ขั้นตอนการติดตั้ง NDK ที่ Kubernetes ต้นทาง

เข้าไปที่ Nutanix Portal เพื่อ copy Download Link เพื่อทำการ download install package และทำการจัดเก็บไว้ใน private registry ภายในสำหรับใช้ในการการติดตั้ง

[nutanix@harbor ~]$ curl -o ndk-1.2.0.tar "https://download.nutanix.com/downloads/ndk/1.2.0/ndk-1.2.0.tar?Expires=1752582997&Key-Pair-Id=APKAJTTNCWPEI42QKMSA&Signature=CGOEgIDQHcJ1fTI8nIMbB5mcrM~5jPFcfS~5PyKDFGQyeNGlfBHyookKrzTTearX6L1aLLyEL6psYlkYIZdDlGIghHQuyb5qQBcxVGqiJ2ENuJD2MZKJkBFb6gnJ5s0JynyfkReAwPU5Ls4Vwb9yZXhzROm25Adezn-noLnkQUpLYQkyNl3~3n3X6xWR7qQQhHbo~QH3GEmYylmfsAcfx78WrH6-t9q3AV-2vhOGFNFz8k4gueqbRAjcLiKJBe7pJ-MlQ1KCo2ZYQMg3OACgqx7epi-2t0ImmpKs0I3rGa0lBqccOggX3n0tfaSED1cwTyjFRRpgFGYWZRX0qdrCvQ__"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  526M  100  526M    0     0  22.5M      0  0:00:23  0:00:23 --:--:-- 30.1M

unzip file ที่ทำการ download มา

[nutanix@harbor ~]$ tar -xvf ndk-1.2.0.tar

Load container เข้าไปยังระบบของ docker

[nutanix@harbor ~]$ docker image load -i ndk-1.2.0/ndk-1.2.0.tar

ทำการ tag container ที่ load เข้าไปในระบบและ push เข้าไปยัง container private registry ที่เตรียมไว้

[nutanix@harbor ~]$ docker image tag ndk/manager:1.2.0 10.38.53.107/ndk/manager:1.2.0
[nutanix@harbor ~]$ docker image tag ndk/infra-manager:1.2.0 10.38.53.107/ndk/infra-manager:1.2.0
[nutanix@harbor ~]$ docker image tag ndk/job-scheduler:1.2.0 10.38.53.107/ndk/job-scheduler:1.2.0
[nutanix@harbor ~]$ docker image tag ndk/kube-rbac-proxy:v0.17.0 10.38.53.107/ndk/kube-rbac-proxy:v0.17.0
[nutanix@harbor ~]$ docker image tag ndk/bitnami-kubectl:1.30.3 

[nutanix@harbor ~]$ docker push 10.38.53.107/ndk/manager:1.2.0
[nutanix@harbor ~]$ docker push 10.38.53.107/ndk/infra-manager:1.2.0
[nutanix@harbor ~]$ docker push 10.38.53.107/ndk/job-scheduler:1.2.0
[nutanix@harbor ~]$ docker push 10.38.53.107/ndk/kube-rbac-proxy:v0.17.0
[nutanix@harbor ~]$ docker push 10.38.53.107/ndk/bitnami-kubectl:1.30.3

ทำการ set environment variable เพื่อ install ndk

[nutanix@harbor ~]$ cd ~/ndk-1.2.0
[nutanix@harbor ~]$ export CLUSTER_NAME=<<NKP cluster to install NDK on>>
[nutanix@harbor ~]$ export KUBECONFIG=~/nkp-v2.15.0/cli/${CLUSTER_NAME}.conf
[nutanix@harbor ~]$ export LOCAL_REG=<<registry hostname/IP>>:<<port>>

ตรวจสอบว่ามี Nutanix CSI credentials secret ในระบบแล้ว ทั้งนี้ nutanix-csi-credentials จะมีอยู่แล้วถ้า NKP ถูกติดตั้งใช้งานกับ Nutanix Infra Provider

kubectl get secret -n ntnx-system

ถ้าไม่มีต้องสร้างด้วย yaml โดยที่ key คือ base64 encoding ของ “<<PC hostname/IP>>:9440:<<PC userid>>:<<PC userid password>>”

apiVersion: v1
data:
  key: <<encoded creds>>
kind: Secret
metadata:
  name: nutanix-csi-credentials
  namespace: ntnx-system
type: Opaque

ติดตั้ง ndk ด้วย helm cli

helm install ndk -n ntnx-system chart/ \
--set manager.repository=${LOCAL_REG}/ndk/manager \
--set manager.tag=1.2.0 \
--set infraManager.repository=${LOCAL_REG}/ndk/infra-manager \
--set infraManager.tag=1.2.0 \
--set kubeRbacProxy.repository=${LOCAL_REG}/ndk/kube-rbac-proxy \ 
--set kubeRbacProxy.tag=v0.17.0 \
--set bitnamiKubectl.repository=${LOCAL_REG}/ndk/bitnami-kubectl \ 
--set bitnamiKubectl.tag=1.30.3 \
--set jobScheduler.repository=${LOCAL_REG}/ndk/job-scheduler \
--set jobScheduler.tag=1.2.0 \
--set tls.server.clusterName=${CLUSTER_NAME} \
--set config.secret.name=nutanix-csi-credentials

ตัวอย่างค่าที่ใช้ในการติดตั้งครั้งนี้

[nutanix@harbor ndk-1.2.0]$ helm install ndk -n ntnx-system chart/ \
--set manager.repository=10.38.53.107/ndk/manager \
--set manager.tag=1.2.0 \
--set infraManager.repository=10.38.53.107/ndk/infra-manager \
--set infraManager.tag=1.2.0 \
--set kubeRbacProxy.repository=10.38.53.107/ndk/kube-rbac-proxy \
--set kubeRbacProxy.tag=v0.17.0 \
--set bitnamiKubectl.repository=10.38.53.107/ndk/bitnami-kubectl \
--set bitnamiKubectl.tag=1.30.3 \
--set jobScheduler.repository=10.38.53.107/ndk/job-scheduler \
--set jobScheduler.tag=1.2.0 \
--set tls.server.clusterName=nkp-at-next \
--set config.secret.name=nutanix-csi-credentials

ตรวจสอบการติดตั้งด้วยการ get pod ndk-controller-manager ว่าทำงานได้ปกติหรือไม่

[nutanix@harbor ndk-1.2.0]$ kubectl get pod -n ntnx-system
NAME                                      READY   STATUS    RESTARTS   AGE
ndk-controller-manager-79687849d5-8vxdh   4/4     Running   0          22s

สร้าง StorageCluster ของทั้งสอง Kubernetes Cluster ที่ต้องการทำ replication ระหว่างกัน โดย Kubernetes Cluster ที่สองจะต้องติดตั้ง NDK ให้สมบูรณ์ตามตัวอย่างข้างต้น

cd ~/nkp-v2.15.0/cli
export CLUSTER_NAME=<<NKP cluster name>>
export KUBECONFIG=~/nkp-v2.15.0/cli/${CLUSTER_NAME}.conf
export PE_UUID=<<PE UUID>>
export PC_UUID=<<PC UUID>>

cat << EOF >  ${CLUSTER_NAME}-storagecluster.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: StorageCluster
metadata:
 name: ${CLUSTER_NAME}
spec:
 storageServerUuid: ${PE_UUID}
 managementServerUuid: ${PC_UUID}
EOF

ตัวอย่างการสร้าง storage cluster

[nutanix@harbor ~]$ export KUBECONFIG=/home/nutanix/nkp-at-next.conf

[nutanix@harbor ~]$ cat << EOF >  nkp-at-next-storagecluster.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: StorageCluster
metadata:
 name: nkp-at-next
spec:
 storageServerUuid: 0006391e-0ad0-299a-70dd-ac1f6b3fddde
 managementServerUuid: f2b78299-aa10-43b9-94d5-faa3527e3897
EOF

[nutanix@harbor ~]$ k apply -f nkp-at-next-storagecluster.yaml
storagecluster.dataservices.nutanix.com/nkp-at-next created
[nutanix@harbor ~]$ k get storagecluster
NAME          AVAILABLE
nkp-at-next   true

สร้าง Storage Cluster ที่ Kubernetes Cluster ที่สอง

[nutanix@harbor ~]$ export KUBECONFIG=cluster2-kubeconfig.yaml

[nutanix@harbor ~]$ cat << EOF >  cluster2-storagecluster.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: StorageCluster
metadata:
 name: cluster2
spec:
 storageServerUuid: 0006391e-0ad0-299a-70dd-ac1f6b3fddde
 managementServerUuid: f2b78299-aa10-43b9-94d5-faa3527e3897
EOF

[nutanix@harbor ~]$ k apply -f cluster2-storagecluster.yaml
storagecluster.dataservices.nutanix.com/cluster2 created
[nutanix@harbor ~]$ k get storagecluster
NAME       AVAILABLE
cluster2   true

สร้าง application resource ที่ต้องการ snapshot หรือ replication โดยตัวอย่างนี้จะ backup resource ทั้งหมดที่อยู่ภายใต้ namespace แต่ถ้าต้องการ filter เฉพาะบาง resource สามารถกำหนดเพิ่มเติมได้ตามตัวอย่างที่ https://portal.nutanix.com/page/documents/details?targetId=Nutanix-Data-Services-for-Kubernetes-v1_2:top-app-cr-manual-create-cli-k8s-t.html

[nutanix@harbor ~]$ cat << EOF >  wordpress-app-cr.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: Application
metadata:
  name: wordpress-app-cr
  namespace: application
spec:
  applicationSelector:
EOF

[nutanix@harbor ~]$ k apply -f wordpress-app-cr.yaml
application.dataservices.nutanix.com/wordpress-app-cr created

[nutanix@harbor ~]$ k get application -A
NAMESPACE     NAME               AGE   ACTIVE   LAST-STATUS-UPDATE
application   wordpress-app-cr   20s            20s

สร้าง application snapshot ที่ cluster ต้นทาง

cd ~/nkp-v2.15.0/cli
export CLUSTER_NAME=<<NKP cluster name>>
export KUBECONFIG=~/nkp-v2.15.0/cli/${CLUSTER_NAME}.conf
export APP_NAME=<<application name>>
export APP_NAMESPACE=<<application namespace>>

cat << EOF >  ${APP_NAME}-snap.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshot
metadata:
  name: ${APP_NAME}-snap
  namespace: ${APP_NAMESPACE}
spec:
  source:
    applicationRef:
     name: ${APP_NAME}-app-cr 
  expiresAfter: <<#>>m
EOF

ตัวอย่างค่าที่ใช้ในตัวอย่างนี้

[nutanix@harbor ~]$ cat << EOF >  wordpress-snap.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshot
metadata:
  name: wordpress-snap
  namespace: application
spec:
  source:
    applicationRef:
     name: wordpress-app-cr
  expiresAfter: 120m
EOF
[nutanix@harbor ~]$ k apply -f wordpress-snap.yaml
applicationsnapshot.dataservices.nutanix.com/wordpress-snap created

[nutanix@harbor ~]$ kubectl get applicationsnapshot -n application
NAME             AGE   READY-TO-USE   BOUND-SNAPSHOTCONTENT                      SNAPSHOT-AGE   CONSISTENCY-TYPE
wordpress-snap   69s   true           asc-531ae7ca-7afb-40a6-a4f6-403c8e732cfa   36s

ตรวจสอบสถานะของการ snapshot

[nutanix@harbor ~]$ kubectl get applicationsnapshotcontent
NAME                                       READY-TO-USE   AGE    APPLICATIONSNAPSHOT   SNAPSHOTCONTENT-AGE   CONSISTENCYTYPE
asc-531ae7ca-7afb-40a6-a4f6-403c8e732cfa   true           110s   wordpress-snap        77s

ทดสอบ restore application จาก snapshot ที่สร้างขึ้นภายใน cluster เดียวกัน แต่ต่าง namspace ตามตัวอย่างนี้ โดยต้องสร้าง reference grant

export APP_NAME=<<application name>>
export APP_NAMESPACE=<<application namespace>>
export TGT_NAMESPACE=<<target namespace>>
***NOTE: target namespace must exist on target cluster (create if necessary)***
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cat << EOF >  ${APP_NAME}-snap-rg.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
 name: ${APP_NAME}-snap-rg
 namespace: ${APP_NAMESPACE}
spec:
 from:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshotRestore
   namespace: ${TGT_NAMESPACE}
 to:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshot
   name: ${APP_NAME}-snap
EOF

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
kubectl apply -f ${APP_NAME}-snap-rg.yaml
- verify reference grant exists:
kubectl get referencegrant -n ${APP_NAMESPACE}
--> ${APP_NAME}-snap-rg is in the list of reference grants

ตัวอย่างค่าที่ใช้ในการสร้าง reference grant

[nutanix@harbor ~]$ k create ns newapplication
namespace/newapplication created

[nutanix@harbor ~]$ cat << EOF >  wordpress-snap-rg.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
 name: wordpress-snap-rg
 namespace: application
spec:
 from:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshotRestore
   namespace: newapplication
 to:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshot
   name: wordpress-snap
EOF

[nutanix@harbor ~]$ k apply -f wordpress-snap-rg.yaml
referencegrant.gateway.networking.k8s.io/wordpress-snap-rg created

[nutanix@harbor ~]$ kubectl get referencegrant -n application
NAME                AGE
wordpress-snap-rg   35s

สร้าง application restore

cat << EOF >  ${APP_NAME}-rg-restore.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotRestore
metadata:
  name: ${APP_NAME}-rg-restore
  namespace: ${TGT_NAMESPACE}
spec:
  applicationSnapshotName: ${APP_NAME}-snap
  applicationSnapshotNamespace: ${APP_NAMESPACE}
EOF
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
kubectl apply -f ${APP_NAME}-rg-restore.yaml
- verify restore operation completes successfully (can take a few minutes)
kubectl get applicationsnapshotrestore -n ${TGT_NAMESPACE}
--> ${APP_NAME}-rg-restore shows a COMPLETED status of 'true'
- verify all artifacts referenced in snapshot describe operation have been restored on the target namespace:
***For example***
kubectl get all -n ${TGT_NAMESPACE}
kubectcl get pvc -n ${TGT_NAMESPACE}

ตัวอย่างการ restore application

[nutanix@harbor ~]$ cat << EOF >  wordpress-rg-restore.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotRestore
metadata:
  name: wordpress-rg-restore
  namespace: newapplication
spec:
  applicationSnapshotName: wordpress-snap
  applicationSnapshotNamespace: application
EOF
[nutanix@harbor ~]$ k apply -f wordpress-rg-restore.yaml
applicationsnapshotrestore.dataservices.nutanix.com/wordpress-rg-restore created

[nutanix@harbor ~]$ kubectl get applicationsnapshotrestore -n newapplication
NAME                   SNAPSHOT-NAME    COMPLETED
wordpress-rg-restore   wordpress-snap   false

[nutanix@harbor ~]$ kubectl get applicationsnapshotrestore -n newapplication
NAME                   SNAPSHOT-NAME    COMPLETED
wordpress-rg-restore   wordpress-snap   true

[nutanix@harbor ~]$ k get all -n newapplication
NAME                                   READY   STATUS    RESTARTS   AGE
pod/wordpress-84f858d9fd-8mjdp         1/1     Running   0          2m24s
pod/wordpress-mysql-556f6f65cc-bjd8q   1/1     Running   0          2m24s

NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/wordpress         ClusterIP   10.99.164.74   <none>        80/TCP     2m24s
service/wordpress-mysql   ClusterIP   None           <none>        3306/TCP   2m24s

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wordpress         1/1     1            1           2m24s
deployment.apps/wordpress-mysql   1/1     1            1           2m24s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/wordpress-84f858d9fd         1         1         1       2m24s
replicaset.apps/wordpress-mysql-556f6f65cc   1         1         1       2m24s

ตัวอย่างการ restore application snapshot ไปยัง kubernetes cluster ปลายทาง โดยต้องสร้าง Remote cluster ที่ cluster ต้นทางก่อน

- verify ndk intercom service is working on both source & target clusters:
cd ~/nkp-v2.15.0/cli
export SOURCE_NAME=<<NKP source cluster name>>
export TARGET_NAME=<<target cluster name>>
export APP_NAME=<<application name>>
export APP_NAMESPACE=<<application namespace>>
export KUBECONFIG=~/nkp-v2.15.0/cli/${SOURCE_NAME}.conf
kubectl get svc -n ntnx-system
--> load balancer service 'ndk-intercom-service' should exist and have an assigned external IP
kubectl get svc -n ntnx-system --kubeconfig ${TARGET_NAME}.conf
--> load balancer service 'ndk-intercom-service' should exist and have an assigned external IP	
--> cache external IP (needed for the remote cr)
- verify an application snapshot exists and is ready:
kubectl get applicationsnapshot -n ${APP_NAMESPACE}
--> snapshot status for <<app name>>-snap should be "true"
- create remote custom resource:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cat << EOF >  ndk-${TARGET_NAME}-remote.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: Remote
metadata:
  name: ndk-${TARGET_NAME}-remote
spec:
  clusterName: ${TARGET_NAME} 
  ndkServiceIp: <<EXTERNAL IP of target cluster's 'ndk-intercom-service' LB service>> 
  ndkServicePort: 2021
  tlsConfig:
    skipTLSVerify: true
EOF
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--> 'tlsConfig' section may not be needed or may need modified based on how NDK was installed, see following link: https://portal.nutanix.com/page/documents/details?targetId=Nutanix-Data-Services-for-Kubernetes-v1_2:top-remote-cr-create-cli-k8s.html
kubectl apply -f ndk-${TARGET_NAME}-remote.yaml
- verify remote cr is ready:
kubectl get remote
--> ndk-${TARGET_NAME}-remote should be listed and AVAILABLE status set to 'True'

ตัวอย่างการสร้าง Remote cluster

[nutanix@harbor ~]$ cat << EOF >  ndk-cluster2-remote.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: Remote
metadata:
  name: ndk-cluster2-remote
spec:
  clusterName: cluster2
  ndkServiceIp: 10.38.53.17
  ndkServicePort: 2021
  tlsConfig:
    skipTLSVerify: true
EOF
[nutanix@harbor ~]$ k apply -f ndk-cluster2-remote.yaml
remote.dataservices.nutanix.com/ndk-cluster2-remote created
[nutanix@harbor ~]$ k get remote
NAME                  ADDRESS       PORT   AVAILABLE
ndk-cluster2-remote   10.38.53.17   2021   True

สร้าง replication target ที่ต้องการให้ snapshot ทำการ replicate ไป

cat << EOF >  ${APP_NAME}-replicate-target.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ReplicationTarget
metadata:
  name: ${TARGET_NAME}
  namespace: ${APP_NAMESPACE}
spec:
  namespaceName: ntnx-system
  remoteName: ndk-${TARGET_NAME}-remote
  serviceAccountName: default
EOF

ตัวอย่างการสร้าง Replication Target

[nutanix@harbor ~]$ cat << EOF >  wordpress-replicate-target.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ReplicationTarget
metadata:
  name: cluster2
  namespace: application
spec:
  namespaceName: ntnx-system
  remoteName: ndk-cluster2-remote
  serviceAccountName: default
EOF
[nutanix@harbor ~]$ k apply -f wordpress-replicate-target.yaml
replicationtarget.dataservices.nutanix.com/cluster2 created
[nutanix@harbor ~]$ k get replicationtarget -A
NAMESPACE     NAME       REMOTE-NAME           REMOTE-NAMESPACE   AVAILABLE
application   cluster2   ndk-cluster2-remote   ntnx-system        True

สร้าง application snapshot replication ไปยัง cluster ปลายทาง

cat << EOF >  ${APP_NAME}-replicate.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotReplication
metadata:
  name: ${APP_NAME}-replicate
  namespace: ${APP_NAMESPACE}
spec:
  applicationSnapshotName: ${APP_NAME}-snap
  replicationTargetName: ${TARGET_NAME}
EOF

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
kubectl apply -f ${APP_NAME}-replicate.yaml
- monitor an application snapshot replication:
kubectl get applicationsnapshotreplication -n ${APP_NAMESPACE}
--> within a few minutes AVAILABLE status for <<app name>>-snap should be "True"
kubectl get applicationsnapshot -n ntnx-system ${APP_NAME}-snap --kubeconfig ${TARGET_NAME}.conf
--> should list snapshot on target cluster with a READY-TO-USE status of "true"

ตัวอย่างการสร้าง application snapshot replication

***NOTE: This enables application snapshots saved in one cluster to be restored to their original namespace on another cluster***
- verify the NDK reference grant CRD exists on the 'target' cluster:
cd ~/nkp-v2.15.0/cli
export TARGET_NAME=<<NKP target cluster name>>
export KUBECONFIG=~/nkp-v2.15.0/cli/${TARGET_NAME}.conf
kubectl get crd | grep 'referencegrants'
--> the 'referencegrants.gateway.networking.k8s.io' cred should be listed
- create a reference grant:
export APP_NAME=<<application name>>
export TGT_NAMESPACE=<<application's namespace on source cluster>>
***NOTE: target namespace must exist on target cluster (create if necessary)***
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cat << EOF >  ${APP_NAME}-snap-rg-${TARGET_NAME}.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
 name: ${APP_NAME}-snap-rg-${TARGET_NAME}
 namespace: ntnx-system
spec:
 from:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshotRestore
   namespace: ${TGT_NAMESPACE}
 to:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshot
   name: ${APP_NAME}-snap
EOF

ตัวอย่างการสร้าง application snapshot replication

[nutanix@harbor ~]$ cat << EOF >  wordpress-replicate.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotReplication
metadata:
  name: wordpress-replicate
  namespace: application
spec:
  applicationSnapshotName: wordpress-snap
  replicationTargetName: cluster2
EOF
[nutanix@harbor ~]$ k apply -f wordpress-replicate.yaml
applicationsnapshotreplication.dataservices.nutanix.com/wordpress-replicate created
[nutanix@harbor ~]$ k get applicationsnapshotreplication -A
NAMESPACE     NAME                  AVAILABLE   APPLICATIONSNAPSHOT   REPLICATIONTARGET   AGE
application   wordpress-replicate   False       wordpress-snap        cluster2            12s

[nutanix@harbor ~]$ k get applicationsnapshotreplication -A
NAMESPACE     NAME                  AVAILABLE   APPLICATIONSNAPSHOT   REPLICATIONTARGET   AGE
application   wordpress-replicate   True        wordpress-snap        cluster2            6m57s

[nutanix@harbor ~]$ kubectl get applicationsnapshot -n ntnx-system
NAME             AGE     READY-TO-USE   BOUND-SNAPSHOTCONTENT                                  SNAPSHOT-AGE
wordpress-snap   9m46s   true           asc-531ae7ca-7afb-40a6-a4f6-403c8e732cfa-1980ca1b120   6m58s

สร้าง reference grant สำหรับการ restore โดยต้องสร้างที่ cluster ปลายทาง

- create a reference grant:
export APP_NAME=<<application name>>
export TGT_NAMESPACE=<<application's namespace on source cluster>>
***NOTE: target namespace must exist on target cluster (create if necessary)***
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cat << EOF >  ${APP_NAME}-snap-rg-${TARGET_NAME}.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
 name: ${APP_NAME}-snap-rg-${TARGET_NAME}
 namespace: ntnx-system
spec:
 from:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshotRestore
   namespace: ${TGT_NAMESPACE}
 to:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshot
   name: ${APP_NAME}-snap
EOF

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
kubectl apply -f ${APP_NAME}-snap-rg-${TARGET_NAME}.yaml
- verify reference grant exists:
kubectl get referencegrant -n ntnx-system
--> ${APP_NAME}-snap-rg-${TARGET_NAME} is in the list of reference grants

ตัวอย่างการสร้าง reference grant

[nutanix@harbor ~]$ cat << EOF >  wordpress-snap-rg-cluster2.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
 name: wordpress-snap-rg-cluster2
 namespace: ntnx-system
spec:
 from:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshotRestore
   namespace: application
 to:
 - group: dataservices.nutanix.com
   kind: ApplicationSnapshot
   name: wordpress-snap
EOF
[nutanix@harbor ~]$ k apply -f wordpress-snap-rg-cluster2.yaml
referencegrant.gateway.networking.k8s.io/wordpress-snap-rg-cluster2 created
[nutanix@harbor ~]$ k get referencegrant -A
NAMESPACE     NAME                         AGE
ntnx-system   wordpress-snap-rg-cluster2   9s

ทำการ restore application ที่ cluster ปลายทาง

cat << EOF >  ${APP_NAME}-rg-restore-${TARGET_NAME}.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotRestore
metadata:
  name: ${APP_NAME}-rg-restore-${TARGET_NAME}
  namespace: ${TGT_NAMESPACE}
spec:
  applicationSnapshotName: ${APP_NAME}-snap
  applicationSnapshotNamespace: ntnx-system
EOF

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
kubectl apply -f ${APP_NAME}-rg-restore-${TARGET_NAME}.yaml
- verify restore operation completes successfully (can take a few minutes)
kubectl get applicationsnapshotrestore -n ${TGT_NAMESPACE}
--> ${APP_NAME}-rg-restore-${TARGET_NAME} shows a COMPLETED status of 'true'
- verify all artifacts referenced in snapshot describe operation have been restored on the target namespace:
***For example***
kubectl get all -n ${TGT_NAMESPACE}
kubectcl get pvc -n ${TGT_NAMESPACE}

ตัวอย่างการ restore application

[nutanix@harbor ~]$ cat << EOF >  wordpress-rg-restore-cluster2.yaml
apiVersion: dataservices.nutanix.com/v1alpha1
kind: ApplicationSnapshotRestore
metadata:
  name: wordpress-rg-restore-cluster2
  namespace: application
spec:
  applicationSnapshotName: wordpress-snap
  applicationSnapshotNamespace: ntnx-system
EOF
[nutanix@harbor ~]$ k apply -f wordpress-rg-restore-cluster2.yaml
applicationsnapshotrestore.dataservices.nutanix.com/wordpress-rg-restore-cluster2 created
[nutanix@harbor ~]$ k get applicationsnapshotrestore -A
NAMESPACE     NAME                            SNAPSHOT-NAME    COMPLETED
application   wordpress-rg-restore-cluster2   wordpress-snap   false

[nutanix@harbor ~]$ k get applicationsnapshotrestore -A
NAMESPACE     NAME                            SNAPSHOT-NAME    COMPLETED
application   wordpress-rg-restore-cluster2   wordpress-snap   true
[nutanix@harbor ~]$ k get pod -n application
NAME                               READY   STATUS    RESTARTS   AGE
wordpress-84f858d9fd-zqtrt         1/1     Running   0          78s
wordpress-mysql-556f6f65cc-s2h8m   1/1     Running   0          78s
[nutanix@harbor ~]$

Update NKP using NKP cli

ในการ upgrade นี้จะใช้วิธีแบบ air gapped โดยต้อง download bundle จาก nutanix portal ด้วยการ copy Download link ของ NKP Airgapped Bundle (Version v2.15.0)

ใช้ curl ในการ download หรือ tools อื่น หรือจะ click download ก็ได้ ตัวอย่างนี้จะใช้ curl

[nutanix@harbor ~]$ curl -o nkp-air-gapped-bundle_v2.15.0_linux_amd64.tar.gz "https://download.nutanix.com/downloads/nkp/v2.15.0/nkp-air-gapped-bundle_v2.15.0_linux_amd64.tar.gz?Expires=1751840997&Key-Pair-Id=APKAJTTNCWPEI42QKMSA&Signature=MWumafkGQqAazC31HnaDyHlqfjAggj8q7c5~viJDgbJ1vb2-Xv1bHJwshl4m2gILPcf8emPtZU4VKk0sOMJ-t7lc9PopCKGhd~T1LPTt6wSJx29MEEdcvE7p3jDQZI1uuI5qflzmwaljOiwXThgiIgnIw3WcVa97a7GT3BH1O~fmg0sbiSFh1LG6lUKMCZayDwlbbSfBQA1feE8WfTolwgXNu6WMtZBn2Rl4l0bffuWVcGDGFn2ywBJSt5V5iPTU4bgjzLOqSB6E3ZkvtBZvIHqNwhrUEPJpKyTTvUZh7wbP56gU~hThlJZBOs6BkC5n6h~E0H2aohDUjUjos396XA__"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 16.2G 100 16.2G 0 0 41.8M 0 0:06:36 0:06:36 --:--:-- 40.9M

ทำการ unzip file ที่ download มา

[nutanix@harbor ~]$ tar -xvf nkp-air-gapped-bundle_v2.15.0_linux_amd64.tar.gz
[nutanix@harbor ~]$ cd nkp-v2.15.0
[nutanix@harbor nkp-v2.15.0]$ ls
application-charts cli kib kubectl NOTICES
application-repositories container-images konvoy-bootstrap-image-v2.15.0.tar nkp-image-builder-image-v2.15.0.tar

push kommander container ซึ่งเป็นระบบจัดการ (nkp management) การใช้งานต่างๆ ของ NKP ไปยัง harbor internal registry

[nutanix@harbor nkp-v2.15.0]$ nkp push bundle --bundle ./container-images/kommander-image-bundle-v2.15.0.tar \
--to-registry=https://10.38.16.169/mirror \
--to-registry-username=admin \
--to-registry-password=Harbor12345 \
--to-registry-insecure-skip-tls-verify
✓ Creating temporary directory
✓ Unarchiving image bundle "./container-images/kommander-image-bundle-v2.15.0.tar"
✓ Parsing image bundle config
✓ Starting temporary Docker registry
✓ Pushing bundled images [================================>135/135] (time elapsed 53s)

push nkp convoy ซึ่งเป็น cluster life cycle management ของ nkp

[nutanix@harbor nkp-v2.15.0]$ nkp push bundle --bundle ./container-images/konvoy-image-bundle-v2.15.0.tar \
--to-registry=https://10.38.16.169/mirror \
--to-registry-username=admin \
--to-registry-password=Harbor12345 \
--to-registry-insecure-skip-tls-verify
✓ Creating temporary directory
✓ Unarchiving image bundle "./container-images/konvoy-image-bundle-v2.15.0.tar"
✓ Parsing image bundle config
✓ Starting temporary Docker registry
✓ Pushing bundled images [================================>110/110] (time elapsed 52s)

update nkp cli เป็น version 2.15 โดยการ copy workdload link และ install cli

curl -fsSL https://raw.githubusercontent.com/nutanixdev/nkp-quickstart/main/get-nkp-cli | bash

run nkp update command เพื่อ update kommander

*** ต้อง upgrade kommander ก่อนเสมอ เพราะการ upgrade nkp management cluster ด้วย version ใหม่ก่อน อาจจะส่งผลให้ kommander ทำงานมีปัญหาได้จากการเปลี่ยนแปลง version ของ kubernetes โดยที่ยังใช้ kommander version เก่าอยู่

[nutanix@harbor nkp-v2.15.0]$ nkp upgrade kommander --charts-bundle ./application-charts/nkp-kommander-charts-bundle-v2.15.0.tar.gz \
--kommander-applications-repository ./application-repositories/kommander-applications-v2.15.0.tar.gz \
--kubeconfig /home/nutanix/nkp-at-next.conf \
--request-timeout 0 \
-v 5

หลังจาก install kommander เสร็จแล้วทำการ update nkp management cluster โดยการ list cluster ทั้งหมด และใช้ nkp cli ตามตัวอย่าง

[nutanix@harbor ~]$ k get cluster -A
NAMESPACE NAME CLUSTERCLASS PHASE AGE VERSION
default nkp-at-next nkp-nutanix-v2.14.0 Provisioned 42h v1.31.4
kommander-default-workspace cluster2 nkp-nutanix-v2.14.0 Provisioned 38h v1.31.4
nkp upgrade cluster nutanix \
--cluster-name nkp-at-next \
--vm-image nkp-rocky-9.5-release-1.32.3-20250430150550.qcow2 \
--kubeconfig /home/nutanix/nkp-at-next.conf \
--timeout 0 -v 5

หลังจาก upgrade management cluster เสร็จแล้ว สามารถ upgrade workload cluster ตามด้วย nkp cli ตามตัวอย่าง

[nutanix@harbor ~]$ nkp upgrade cluster nutanix \
--cluster-name cluster2 \
--namespace kommander-default-workspace \
--vm-image nkp-rocky-9.5-release-1.32.3-20250430150550.qcow2 \
--kubeconfig /home/nutanix/nkp-at-next.conf \
--timeout 0 -v 6

ตรวจสอบว่า cluster ถูก upgrade ไปยัง version ใหม่แล้วด้วย cli

[nutanix@harbor ~]$ k get cluster -A
NAMESPACE NAME CLUSTERCLASS PHASE AGE VERSION
default nkp-at-next nkp-nutanix-v2.15.0 Provisioned 43h v1.32.3
kommander-default-workspace cluster2 nkp-nutanix-v2.15.0 Provisioned 39h v1.32.3

Create Self-Sign for Kubernetes Ingress (TLS)

การออกกใบรับรอง Digital Certificate จะออกโดย Certificate Authority (CA) ซึ่งเป็นหน่วยงานกลางที่ได้รับความน่าเชื่อถือ เช่น GlobalSign, DigiCert, GoDaddy, Let’s Encrypt หรือถ้าหน่วยงานที่มี Certificate Server ภายในไว้ออก digital certificate โดยมี Certificate Server เช่น Microsoft Certificate Services (Windows Server), Hashicorp Vault เป็นต้น

รูปแบบของ Certificate จะแบ่งเป็นลำดับชั้น (Chain) ดังนี้

Root CA
├── Intermediate CA 1 (signed by Root CA)
│ ├── End-entity certificate (signed by Intermediate CA 1)
│ └── End-entity certificate (signed by Intermediate CA 1)
└── Intermediate CA 2 (signed by Root CA)
└── End-entity certificate (signed by Intermediate CA 2)

Root CA เป็น Top-level ของ Certificate hierarchy สำหรับ Public Key Infrastructure (PKI) มีความน่าเชื่อถือสูง และมักถูกเก็บไว้แบบ offline เพื่อความปลอดภัย เช่น trust store ของเบราว์เซอร์ต่างๆ และใช้เพื่อออก intermediate certificate หรือใบรับรองอื่นๆ

Intermediate CA เป็น CA ที่อยู่ระหว่าง root และ end-entity certificate ซึ่งใช้ในการออก certificate ให้กลับผู้ร้องขอของระบบย่อยลงไป หรือผู้ใช้งาน (end-entity certificate) เช่นเว็บไซต์ อีเมลล์​ ช่วยลดความเสี่ยงในการถูกโจมตี เพราะหาก intermediate certificate ถูกโจมตี จะสามารถเพิกถอนได้ง่ายกว่า Root Certificate

Root Certificate จึงเป็นรากฐานของความน่าเชื่อถือในระบบ PKI (public key infrastructure) ในขณะที่ Intermediate Certificate ทำหน้าที่เป็นสะพานเชื่อระหว่าง Root Certificate และใบรับรองอื่นๆ ที่ใช้งานจริง

ขั้นตอนสร้าง Self-Sign ฉertificate จาการสร้าง CA เองมีขั้นตอนดังนี้

สร้าง CA key

openssl genrsa -out ca.key 4096

สร้าง CA Certificate จาก CA key ที่สร้างก่อนหน้า

openssl req -x509 -new -nodes -sha512 -days 3650 \
-subj "/C=TH/ST=TH/L=BK/O=IT/OU=IT/CN=Corp-Internal-CA Root CA" \
-key ca.key \
-out ca.crt

สร้าง Server key หรือใบรับรองของผู้ร้องขอ Digital Certificate (end-entity certificate)

openssl genrsa -out app.key 4096

ทำการสร้าง CSR (Certificate Signing Requests) เพื่อส่งให้กับ CA ทำการ sign หรือออก Digital Certificate ให้

openssl req -sha512 -new \
-subj "/C=TH/ST=TH/L=BK/O=IT/OU=IT/CN=*.app.corp.com" \
-key app.key \
-out app.csr

กระบวนการออก certificate ของ CA จะสร้างส่วนของ SAN เพื่อที่จะมีข้อมูลของ server เช่น server fqdn หรือ ip ในการระบุตัวตนที่ออกโดย CA

[~/repos/lab/test-secret] cat v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.0=app.corp.com
DNS.0=*.app.corp.com
IP.0=10.38.16.141

CA ทำการออก Digital Certificate โดยการ sign ด้วย Certificate ของ CA จาก Certificate Signing Request (CSR) ของผู้ร้องขอ Digital Certificate

openssl x509 -req -sha512 -days 3650 \
-extfile v3.ext \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-in app.csr \
-out app.crt
Certificate request self-signature ok
subject=C=TH, ST=TH, L=BK, O=IT, OU=IT, CN=*.app.corp.com

จากนั้น CA จะส่ง Certificate ที่สร้างขึ้นส่งกลับไปยังผู้ร้องขอ ซึ่งก็คือ app.crt ตามตัวอย่างเพื่อใช้งานต่อไป เช่นไปใช้กับ kubernetes Ingress หรือ Application Server เป็นต้น

Nutanix NKP Backup With Velero

ทำการ enable velero ที่ nkp dashboard เนื่องจาก velero ต้องการ s3 storage เพื่อเก็บข้อมูล backup จึงต้อง enable rookceph และ rookceph cluster โดยจะต้องรอจนกว่าจะ running สมบูรณ์ก่อนจะ enable velero

ขั้นตอน enable จะต้อง set ค่า configuration เพิ่มเติม โดยต้องสร้าง secret สำหรับให้ velero ใช้งาน S3 Storage โดยสร้าง secret key ได้ตามตัวอย่างดังนี้

apiVersion: v1
kind: Secret
metadata:
name: pongsak-velero-ceph
namespace: kommander-default-workspace
type: Opaque
stringData:
aws: |
[default]
aws_access_key_id = HUD5Z1JPRDS8IQOA5U5G
aws_secret_access_key = ASvtTAAUPtcbEEpCnCuvczQgF3N4hiTIn9qUruzA

ทำการ enable Velero โดยใช้ configuration ตามตัวอย่างนี้

เพิ่ม custom config เพื่อให้ Velero สามารถ backup persistent volume ได้

configuration:
backupStorageLocation:
- bucket: backup
config:
region: dkp-object-store
s3Url: https://10.38.37.19:8085
s3ForcePathStyle: "true"
insecureSkipTLSVerify: true
profile: default
provider: aws
credential:
key: aws
name: pongsak-velero-ceph
features: EnableCSI
uploaderType: kopia
volumeSnapshotLocation:
- bucket: backup
config:
region: dkp-object-store
s3Url: https://10.38.37.19:8085
provider: aws
deployNodeAgent: true
initContainers:
- image: velero/velero-plugin-for-aws:v1.13.2
imagePullPolicy: IfNotPresent
name: velero-plugin-for-aws
volumeMounts:
- mountPath: /target
name: plugins
metrics:
enabled: true
serviceMonitor:
enabled: true
nodeAgent:
annotations:
secret.reloader.stakater.com/reload: pongsak-velero-ceph
priorityClassName: dkp-critical-priority
resources:
limits: null

หลังจาก enable แล้วตรวจสอบว่า velero pod ทำงานได้ปกติ รวมถึงตรวจสอบ backup location และ volume snapshot location

[nutanix@harbor ~]$ kubectl get pod -A | grep velero
kommander-default-workspace object-bucket-claims-check-dkp-velero-xmxsx 0/1 Completed 0 3h1m
kommander-default-workspace velero-858485b79-b9rdc 1/1 Running 0 72s
kommander-default-workspace velero-backup-storage-location-updater-d4d54cf6f-rhccm 1/1 Running 0 14s
kommander-default-workspace velero-pre-install-cr4rf 0/1 Completed 0 105s
[nutanix@harbor ~]$ kubectl get backupstoragelocations -A
NAMESPACE NAME PHASE LAST VALIDATED AGE DEFAULT
kommander-default-workspace default Available 42s 13m true
[nutanix@harbor ~]$ kubectl get volumesnapshotlocations -A
NAMESPACE NAME AGE
kommander-default-workspace default 14m

ติดตั้ง Velero cli สำหรับการใช้งาน

# For Apple Silicon Macs (M1/M2/M3) Download ARM64 version
curl -LO https://github.com/vmware-tanzu/velero/releases/latest/download/velero-darwin-arm64.tar.gz
# Extract and install
tar -xzf velero-darwin-arm64.tar.gz
sudo mv velero-*/velero /usr/local/bin/
chmod +x /usr/local/bin/velero
rm -rf velero-* velero-darwin-arm64.tar.gz
#Ubuntu/Debian (APT)
# Add Velero repository
curl -s https://packagecloud.io/install/repositories/vmware/velero/script.deb.sh | sudo bash
# Install Velero
sudo apt-get install velero
#CentOS/RHEL/Fedora (YUM/DNF)
# Add Velero repository
curl -s https://packagecloud.io/install/repositories/vmware/velero/script.rpm.sh | sudo bash
# Install Velero (CentOS/RHEL)
sudo yum install velero
# Or for Fedora
sudo dnf install velero

ตรวจสอบ version ของ Velero หลังจากติดตั้ง

[nutanix@harbor ~]$ velero version
Client:
Version: v1.17.2
Git commit: 7013a4097f17a97a0201c0ad3c9dd3f810d32bf6

Velero จะใช้ kubeconfig file เพื่อ connect ไปยัง kubernetes จึงต้อง export kubeconfig environment ก่อน จากนั้นตรวจสอบว่า velero เห็น backup location หรือไม่

[nutanix@harbor ~]$ export KUBECONFIG=/home/nutanix/dev2-kubeconfig.conf
[nutanix@harbor ~]$ velero get backup-location -n kommander-default-workspace
NAME PROVIDER BUCKET/PREFIX PHASE LAST VALIDATED ACCESS MODE DEFAULT
default aws backup Available 2026-01-30 11:25:11 +0000 UTC ReadWrite true
[nutanix@harbor ~]$ velero get snapshot-location -n kommander-default-workspace
NAME PROVIDER
default aws

ติดตั้ง wordpress เพื่อใช้ในการทดสอบ backup และ restore โดยติดตั้งที่ namespace application ตามตัวอย่าง yaml ดังนี้

apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
stringData:
password: nutanix
---
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: mysql:8.0
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
---
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:6.4-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_NAME
value: wordpress
- name: WORDPRESS_DEBUG
value: "1"
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
ingressClassName: kommander-traefik
rules:
- host: wordpress.10.38.37.19.sslip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress
port:
number: 80

หมายเหตุ – “10.38.37.19” คือ ingress ip

ตรวจสอบว่า wordpress ทำงานได้ปกติ และเพิ่ม page เข้าไปใน wordpress เพื่อใช้ในการตรวจสอบการ restore

~/repos/lab  k get pod -n application
NAME READY STATUS RESTARTS AGE
wordpress-57ff4b4485-6tl9g 1/1 Running 0 10h
wordpress-mysql-6dd978bc8b-jr2gq 1/1 Running 0 10h

เข้า wordpress ที่ ur https://wordpress.10.38.37.19.sslip.io/ และทำการสร้าง page เพื่อตรวจอสอบผลการ restore

ทำการ backup wordpress ด้วย velero cli โดย backup ทั้ง default namespace

[nutanix@harbor ~]$ velero backup create wordpress-backup-1 --include-namespaces default --snapshot-volumes=true -n kommander-default-workspace --wait
Backup request "wordpress-backup-1" submitted successfully.
Waiting for backup to complete. You may safely press ctrl-c to stop waiting - your backup will continue in the background.
...........
Backup completed with status: Completed. You may check for more information using the commands `velero backup describe wordpress-backup-1` and `velero backup logs wordpress-backup-1`.

ตรวจสอบข้อมูลที่ velero ทำการ backup ได้จาก cli ดังนี้

[nutanix@harbor ~]$ velero backup describe wordpress-backup-1 --insecure-skip-tls-verify -n kommander-default-workspace --details
Name: wordpress-backup-1
Namespace: kommander-default-workspace
Labels: velero.io/storage-location=default
Annotations: velero.io/resource-timeout=10m0s
velero.io/source-cluster-k8s-gitversion=v1.34.1
velero.io/source-cluster-k8s-major-version=1
velero.io/source-cluster-k8s-minor-version=34
Phase: Completed
Namespaces:
Included: default
Excluded: <none>
Resources:
Included cluster-scoped: <none>
Excluded cluster-scoped: volumesnapshotcontents.snapshot.storage.k8s.io
Included namespace-scoped: *
Excluded namespace-scoped: volumesnapshots.snapshot.storage.k8s.io
Label selector: <none>
Or label selector: <none>
Storage Location: default
Velero-Native Snapshot PVs: true
File System Backup (Default): false
Snapshot Move Data: false
Data Mover: velero
TTL: 720h0m0s
CSISnapshotTimeout: 10m0s
ItemOperationTimeout: 4h0m0s
Hooks: <none>
Backup Format Version: 1.1.0
Started: 2026-01-30 11:35:35 +0000 UTC
Completed: 2026-01-30 11:35:47 +0000 UTC
Expiration: 2026-03-01 11:35:35 +0000 UTC
Total items to be backed up: 56
Items backed up: 56
Backup Item Operations:
Operation for volumesnapshots.snapshot.storage.k8s.io default/velero-wp-pv-claim-skrl8:
Backup Item Action Plugin: velero.io/csi-volumesnapshot-backupper
Operation ID: default/velero-wp-pv-claim-skrl8/2026-01-30T11:35:41Z
Items to Update:
volumesnapshots.snapshot.storage.k8s.io default/velero-wp-pv-claim-skrl8
volumesnapshotcontents.snapshot.storage.k8s.io /snapcontent-305912d8-4ec2-484e-9273-c2ea65992c55
Phase: Completed
Created: 2026-01-30 11:35:41 +0000 UTC
Started: 2026-01-30 11:35:41 +0000 UTC
Updated: 2026-01-30 11:35:46 +0000 UTC
Operation for volumesnapshots.snapshot.storage.k8s.io default/velero-mysql-pv-claim-rbqkn:
Backup Item Action Plugin: velero.io/csi-volumesnapshot-backupper
Operation ID: default/velero-mysql-pv-claim-rbqkn/2026-01-30T11:35:46Z
Items to Update:
volumesnapshots.snapshot.storage.k8s.io default/velero-mysql-pv-claim-rbqkn
volumesnapshotcontents.snapshot.storage.k8s.io /snapcontent-9681ba15-76fd-4b98-8411-9c083793a14e
Phase: Completed
Created: 2026-01-30 11:35:46 +0000 UTC
Started: 2026-01-30 11:35:46 +0000 UTC
Updated: 2026-01-30 11:35:46 +0000 UTC
Resource List:
apiextensions.k8s.io/v1/CustomResourceDefinition:
- ciliumendpoints.cilium.io
apps/v1/Deployment:
- default/wordpress
- default/wordpress-mysql
apps/v1/ReplicaSet:
- default/wordpress-645b586f68
- default/wordpress-mysql-5ccb49cfb
cilium.io/v2/CiliumEndpoint:
- default/wordpress-645b586f68-dbwgz
- default/wordpress-mysql-5ccb49cfb-rxwtt
discovery.k8s.io/v1/EndpointSlice:
- default/kubernetes
- default/wordpress-dfcfz
- default/wordpress-mysql-jvhnt
networking.k8s.io/v1/Ingress:
- default/wordpress-ingress
snapshot.storage.k8s.io/v1/VolumeSnapshot:
- default/velero-mysql-pv-claim-rbqkn
- default/velero-wp-pv-claim-skrl8
snapshot.storage.k8s.io/v1/VolumeSnapshotClass:
- nutanix-snapshot-class
snapshot.storage.k8s.io/v1/VolumeSnapshotContent:
- snapcontent-305912d8-4ec2-484e-9273-c2ea65992c55
- snapcontent-9681ba15-76fd-4b98-8411-9c083793a14e
v1/ConfigMap:
- default/kube-root-ca.crt
v1/Endpoints:
- default/kubernetes
- default/wordpress
- default/wordpress-mysql
v1/Event:
- default/mysql-pv-claim.188f7ebeb41953b0
- default/mysql-pv-claim.188f7ebeb69531d4
- default/mysql-pv-claim.188f7ebeb6b8fdd8
- default/mysql-pv-claim.188f7ebfa2a36c2e
- default/wordpress-645b586f68-dbwgz.188f7ebfa7ff0ffd
- default/wordpress-645b586f68-dbwgz.188f7ec107862336
- default/wordpress-645b586f68-dbwgz.188f7ec1dd681605
- default/wordpress-645b586f68-dbwgz.188f7ec50eaead7b
- default/wordpress-645b586f68-dbwgz.188f7ec5112993aa
- default/wordpress-645b586f68-dbwgz.188f7ec5164380f9
- default/wordpress-645b586f68.188f7ebeb8bd04b2
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ebfa5454bde
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ec1009f740f
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ec1c720235a
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ec53b9f0b43
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ec53d9cdaf2
- default/wordpress-mysql-5ccb49cfb-rxwtt.188f7ec542ec7a5e
- default/wordpress-mysql-5ccb49cfb.188f7ebeb5c2a7f3
- default/wordpress-mysql.188f7ebeb540f2c9
- default/wordpress.188f7ebeb782a76e
- default/wp-pv-claim.188f7ebeb60eb52f
- default/wp-pv-claim.188f7ebeb9ead9ae
- default/wp-pv-claim.188f7ebeba1102f5
- default/wp-pv-claim.188f7ebfa2bccfc7
v1/Namespace:
- default
v1/PersistentVolume:
- pvc-cdcf92fb-8b3e-4d6a-8751-4d6b7dc31cf3
- pvc-e3188791-445f-4789-8f6c-5bd7c7f82134
v1/PersistentVolumeClaim:
- default/mysql-pv-claim
- default/wp-pv-claim
v1/Pod:
- default/wordpress-645b586f68-dbwgz
- default/wordpress-mysql-5ccb49cfb-rxwtt
v1/Secret:
- default/mysql-pass
v1/Service:
- default/kubernetes
- default/wordpress
- default/wordpress-mysql
v1/ServiceAccount:
- default/default
Backup Volumes:
Velero-Native Snapshots: <none included>
CSI Snapshots:
default/mysql-pv-claim:
Snapshot:
Operation ID: default/velero-mysql-pv-claim-rbqkn/2026-01-30T11:35:46Z
Snapshot Content Name: snapcontent-9681ba15-76fd-4b98-8411-9c083793a14e
Storage Snapshot ID: NutanixVolumes-685b4989-23b6-4d8e-be83-8784ad5e7992:8e517250-1f33-4ee1-a9e1-0d523e8c442c
Snapshot Size (bytes): 21474836480
CSI Driver: csi.nutanix.com
Result: succeeded
default/wp-pv-claim:
Snapshot:
Operation ID: default/velero-wp-pv-claim-skrl8/2026-01-30T11:35:41Z
Snapshot Content Name: snapcontent-305912d8-4ec2-484e-9273-c2ea65992c55
Storage Snapshot ID: NutanixVolumes-86c441ab-529c-4966-bceb-c2856f18a94c:2456bb0f-8792-46f2-8366-8067d5305cde
Snapshot Size (bytes): 21474836480
CSI Driver: csi.nutanix.com
Result: succeeded
Pod Volume Backups: <none included>
HooksAttempted: 0
HooksFailed: 0

ตรวจสอบสถานะการ backup

[nutanix@harbor ~]$ velero get backup -n kommander-default-workspace
NAME STATUS ERRORS WARNINGS CREATED EXPIRES STORAGE LOCATION SELECTOR
wordpress-backup-1 Completed 0 0 2026-01-30 11:35:35 +0000 UTC 29d default <none>

ทำการ ลบ wordpress application

[nutanix@harbor ~]$ kubectl delete -f wordpress.yaml
secret "mysql-pass" deleted
service "wordpress-mysql" deleted
persistentvolumeclaim "mysql-pv-claim" deleted
deployment.apps "wordpress-mysql" deleted
service "wordpress" deleted
persistentvolumeclaim "wp-pv-claim" deleted
deployment.apps "wordpress" deleted
ingress.networking.k8s.io "wordpress-ingress" deleted

ทำการ restore wordpress application ทั้งนี้สามารถ restore จากอีก cluster ได้โดยทั้งสอง cluster จะต้องชี้ไปยัง S3 bucket เดียวกัน

[nutanix@harbor ~]$ velero restore create wordpress-restore-1 --from-backup wordpress-backup-1 --restore-volumes=true -n kommander-default-workspace --wait
Restore request "wordpress-restore-1" submitted successfully.
Waiting for restore to complete. You may safely press ctrl-c to stop waiting - your restore will continue in the background.
..
Restore completed with status: Completed. You may check for more information using the commands `velero restore describe wordpress-restore-1` and `velero restore logs wordpress-restore-1`.

เรียกใช้ wordpress เพื่อตรวจสอบว่าข้อมูลมีการ restore กลับมาครบถ้วน