Category Archives: DevOps

Install Kafka with KRaft mode

Kafka รองรับ KRaft mode (Kafka Raft) ตั้งแต่ version 3.3+ ทำให้ไม่ต้องใช้ Zookeeper เหมือนแต่ก่อน สิ่งที่แตกต่างคือ

  • No Zookeeper – Kafka สามารจัดการ Metadata ได้เองด้วย Raft consensus
  • KAFKA_PROCESS_ROLES: broker, controller – สามารถจัดการ broker และ controller ด้วย node เดียว
  • KAFKA_CONTROLLER_QUORUM_VOTERS – ทนแทนการทำ Leader election ผ่าน Zookeeper
  • จำนวน pod น้อยลง จำนวน resource ที่ใช้น้อยลง และ architecture โดยรวมง่ายขึ้น

ตัวอย่างนี้เราจะ setup Kafka จำนวน 3 replicas เพื่อใช้งานสำหรับ prouction ด้วยมี configuration ดังนี้

  • 3 Kafka replicas – ใช้ podManagementPolicy: Parallel ทำให้ startup ได้เร็ว
  • Dynamic NODE_ID – ใช้ค่าจาก hostname (kafka-0 -> 0, kafka-1 -> 1, kafka-2 -> 2)
  • Dynamic ADVERTISED_LISTENERS – แต่ละ pod จะ advertises hostname ของตัวเอง
  • 3 controller votes : 0@kafka-0.kafka:9093, 1@kafka-1.kafka:9093,2@kafka-2.kafka:9093
  • Replication factor: 3 และ min.insync.replicas:2 สำหรับ fault tolerance (Quorum + fault tolerance)
  • Proper Cluster ID: สร้าง cluster id จาก kafka-storage random-uuid
  • Schema Registry: 2 Replicas สำหรับ leader election ที่จะทำให้มี Active และ Standby สำหรับกรณี failover (active/standby failover)
  • Control Center: 1 Replica สำหรับ monitor UI เนื่องจากเป็น component ที่ไม่ critical และไม่เกี่ยวข้องกับการทำงานของระบบจึงไม่จำเป็นต้องมีหลาย replicas
Shell
#!/bin/bash
#===============================================================================
# install-kafka-kraft.sh
# Deploy Confluent Kafka Stack on Kubernetes (KRaft mode - No Zookeeper)
# Components: Kafka 3-node cluster, Schema Registry, Control Center
#===============================================================================
set -euo pipefail
NAMESPACE="kafka"
GREEN='\033[0;32m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
#===============================================================================
# Step 1: Create Namespace
#===============================================================================
info "Creating namespace '${NAMESPACE}'..."
kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
#===============================================================================
# Step 2: Generate Cluster ID
#===============================================================================
CLUSTER_ID=$(docker run --rm confluentinc/cp-kafka:7.6.0 kafka-storage random-uuid 2>/dev/null || cat /proc/sys/kernel/random/uuid | base64 | head -c 22)
info "Generated Kafka Cluster ID: ${CLUSTER_ID}"
#===============================================================================
# Step 3: Deploy Kafka 3-node cluster in KRaft Mode
#===============================================================================
info "Deploying Kafka 3-node cluster (KRaft mode - no Zookeeper)..."
cat > /tmp/kafka.yaml <<OUTER
---
apiVersion: v1
kind: Service
metadata:
name: kafka
labels:
app: kafka
spec:
ports:
- port: 9092
name: internal
- port: 29092
name: external
- port: 9093
name: controller
clusterIP: None
selector:
app: kafka
---
apiVersion: v1
kind: Service
metadata:
name: kafka-external
labels:
app: kafka
spec:
type: LoadBalancer
ports:
- port: 9092
targetPort: 9092
name: external
selector:
app: kafka
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: kafka
replicas: 3
podManagementPolicy: Parallel
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
securityContext:
fsGroup: 1000
initContainers:
- name: fix-permissions
image: busybox:1.35
command: ["sh", "-c", "rm -rf /var/lib/kafka/data/lost+found && chown -R 1000:1000 /var/lib/kafka/data"]
volumeMounts:
- name: kafka-data
mountPath: /var/lib/kafka/data
containers:
- name: kafka
image: confluentinc/cp-kafka:7.6.0
ports:
- containerPort: 9092
- containerPort: 29092
- containerPort: 9093
env:
- name: CLUSTER_ID
value: "${CLUSTER_ID}"
- name: KAFKA_PROCESS_ROLES
value: "broker,controller"
- name: KAFKA_CONTROLLER_QUORUM_VOTERS
value: "0@kafka-0.kafka:9093,1@kafka-1.kafka:9093,2@kafka-2.kafka:9093"
- name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
value: "PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT"
- name: KAFKA_CONTROLLER_LISTENER_NAMES
value: "CONTROLLER"
- name: KAFKA_INTER_BROKER_LISTENER_NAME
value: "PLAINTEXT"
- name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
value: "3"
- name: KAFKA_TRANSACTION_STATE_LOG_MIN_ISR
value: "2"
- name: KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR
value: "3"
- name: KAFKA_DEFAULT_REPLICATION_FACTOR
value: "3"
- name: KAFKA_MIN_INSYNC_REPLICAS
value: "2"
- name: KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS
value: "0"
- name: KAFKA_AUTO_CREATE_TOPICS_ENABLE
value: "true"
- name: KAFKA_LOG_RETENTION_HOURS
value: "168"
- name: KAFKA_LOG_DIRS
value: "/var/lib/kafka/data"
command:
- /bin/bash
- -c
- |
# Derive NODE_ID from hostname (kafka-0 -> 0, kafka-1 -> 1, kafka-2 -> 2)
export KAFKA_NODE_ID=\${HOSTNAME##*-}
export KAFKA_LISTENERS="PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:29092"
export KAFKA_ADVERTISED_LISTENERS="PLAINTEXT://\${HOSTNAME}.kafka:9092,PLAINTEXT_HOST://\${HOSTNAME}.kafka:29092"
exec /etc/confluent/docker/run
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
volumeMounts:
- name: kafka-data
mountPath: /var/lib/kafka/data
volumeClaimTemplates:
- metadata:
name: kafka-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
OUTER
kubectl apply -n ${NAMESPACE} -f /tmp/kafka.yaml
info "Waiting for Kafka to be ready..."
kubectl rollout status statefulset/kafka -n ${NAMESPACE} --timeout=300s
#===============================================================================
# Step 4: Deploy Schema Registry
#===============================================================================
info "Deploying Schema Registry..."
cat > /tmp/schema-registry.yaml <<'EOF'
---
apiVersion: v1
kind: Service
metadata:
name: schema-registry
labels:
app: schema-registry
spec:
ports:
- port: 8081
name: http
selector:
app: schema-registry
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: schema-registry
spec:
replicas: 2
selector:
matchLabels:
app: schema-registry
template:
metadata:
labels:
app: schema-registry
spec:
enableServiceLinks: false
containers:
- name: schema-registry
image: confluentinc/cp-schema-registry:7.6.0
ports:
- containerPort: 8081
env:
- name: PORT
value: "8081"
- name: SCHEMA_REGISTRY_HOST_NAME
value: "schema-registry"
- name: SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS
value: "kafka-0.kafka:9092,kafka-1.kafka:9092,kafka-2.kafka:9092"
- name: SCHEMA_REGISTRY_LISTENERS
value: "http://0.0.0.0:8081"
- name: SCHEMA_REGISTRY_KAFKASTORE_TOPIC
value: "_schemas"
- name: SCHEMA_REGISTRY_DEBUG
value: "true"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
readinessProbe:
httpGet:
path: /
port: 8081
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
livenessProbe:
httpGet:
path: /
port: 8081
initialDelaySeconds: 120
periodSeconds: 15
failureThreshold: 10
EOF
kubectl apply -n ${NAMESPACE} -f /tmp/schema-registry.yaml
info "Waiting for Schema Registry to be ready..."
kubectl rollout status deployment/schema-registry -n ${NAMESPACE} --timeout=120s
#===============================================================================
# Step 5: Deploy Control Center
#===============================================================================
info "Deploying Control Center..."
cat > /tmp/control-center.yaml <<'EOF'
---
apiVersion: v1
kind: Service
metadata:
name: control-center
labels:
app: control-center
spec:
type: LoadBalancer
ports:
- port: 9021
targetPort: 9021
name: http
selector:
app: control-center
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: control-center
spec:
replicas: 1
selector:
matchLabels:
app: control-center
template:
metadata:
labels:
app: control-center
spec:
enableServiceLinks: false
containers:
- name: control-center
image: confluentinc/cp-enterprise-control-center:7.6.0
ports:
- containerPort: 9021
env:
- name: CONTROL_CENTER_BOOTSTRAP_SERVERS
value: "kafka-0.kafka:9092,kafka-1.kafka:9092,kafka-2.kafka:9092"
- name: CONTROL_CENTER_SCHEMA_REGISTRY_URL
value: "http://schema-registry:8081"
- name: CONTROL_CENTER_REPLICATION_FACTOR
value: "3"
- name: CONTROL_CENTER_INTERNAL_TOPICS_PARTITIONS
value: "3"
- name: CONTROL_CENTER_MONITORING_INTERCEPTOR_TOPIC_PARTITIONS
value: "3"
- name: CONFLUENT_METRICS_TOPIC_REPLICATION
value: "3"
- name: PORT
value: "9021"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
readinessProbe:
httpGet:
path: /
port: 9021
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
livenessProbe:
httpGet:
path: /
port: 9021
initialDelaySeconds: 120
periodSeconds: 15
failureThreshold: 10
EOF
kubectl apply -n ${NAMESPACE} -f /tmp/control-center.yaml
info "Waiting for Control Center to be ready..."
kubectl rollout status deployment/control-center -n ${NAMESPACE} --timeout=300s
#===============================================================================
# Step 6: Cleanup temp files
#===============================================================================
rm -f /tmp/kafka.yaml /tmp/schema-registry.yaml /tmp/control-center.yaml
#===============================================================================
# Step 7: Summary
#===============================================================================
info "============================================"
info "Kafka Stack (KRaft) Deployed Successfully!"
info "============================================"
echo ""
echo " Mode: KRaft (No Zookeeper)"
echo " Cluster ID: ${CLUSTER_ID}"
echo " Replicas: 3"
echo " Namespace: ${NAMESPACE}"
echo " Kafka Brokers: kafka-0.kafka:9092, kafka-1.kafka:9092, kafka-2.kafka:9092"
echo " Schema Registry: schema-registry.${NAMESPACE}:8081"
echo ""
info "Get external IPs:"
echo " kubectl get svc -n ${NAMESPACE}"
echo ""
info "Check all pods:"
echo " kubectl get pods -n ${NAMESPACE}"
echo ""
info "Test Kafka:"
echo " kubectl exec -n ${NAMESPACE} kafka-0 -- kafka-topics --bootstrap-server localhost:9092 --list"
echo " kubectl exec -n ${NAMESPACE} kafka-0 -- kafka-topics --bootstrap-server localhost:9092 --create --topic test --partitions 3 --replication-factor 3"
echo " kubectl exec -n ${NAMESPACE} kafka-0 -- kafka-metadata --snapshot /var/lib/kafka/data/__cluster_metadata-0/00000000000000000000.log --cluster-id ${CLUSTER_ID}"

ผลลัพธ์จากการ run script

deployment "control-center" successfully rolled out
[INFO] ============================================
[INFO] Kafka Stack (KRaft) Deployed Successfully!
[INFO] ============================================
Mode: KRaft (No Zookeeper)
Cluster ID: FF-0MIbaQDmDQuAZoEI9kQ
Replicas: 3
Namespace: kafka
Kafka Brokers: kafka-0.kafka:9092, kafka-1.kafka:9092, kafka-2.kafka:9092
Schema Registry: schema-registry.kafka:8081
[INFO] Get external IPs:
kubectl get svc -n kafka
[INFO] Check all pods:
kubectl get pods -n kafka
[INFO] Test Kafka:
# Check topic details and replication
kubectl exec -n kafka kafka-0 -- kafka-topics --bootstrap-server localhost:9092 --describe --topic test
# Test producing a message
kubectl exec -n kafka kafka-0 -- bash -c 'echo "hello kafka" | kafka-console-producer --bootstrap-server localhost:9092 --topic test'
# Test consuming the message
kubectl exec -n kafka kafka-0 -- kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning --max-messages 1

ทดสอบการใช้งานตามตัวอย่าง

[nutanix@nkp-boot ~]$ kubectl get svc -n kafka
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
control-center LoadBalancer 10.96.18.27 10.55.39.63 9021:30541/TCP 4m51s
kafka ClusterIP None <none> 9092/TCP,29092/TCP,9093/TCP 25m
kafka-external LoadBalancer 10.103.153.22 10.55.39.62 9092:31267/TCP 25m
schema-registry ClusterIP 10.102.209.166 <none> 8081/TCP 24m
[nutanix@nkp-boot ~]$ kubectl get pods -n kafka
NAME READY STATUS RESTARTS AGE
control-center-6969748bd5-kpkzq 1/1 Running 0 4m56s
kafka-0 1/1 Running 0 6m13s
kafka-1 1/1 Running 0 6m26s
kafka-2 1/1 Running 0 6m36s
schema-registry-6fb4f4b8b8-btmcz 1/1 Running 0 5m59s
schema-registry-6fb4f4b8b8-dwqhg 1/1 Running 0 18m
[nutanix@nkp-boot ~]$ kubectl exec -n kafka kafka-0 -- kafka-topics --bootstrap-server localhost:9092 --describe --topic test
Defaulted container "kafka" out of: kafka, fix-permissions (init)
Topic: test TopicId: SwmGm8XkQf6XRsZp5Gkobg PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2
Topic: test Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: test Partition: 1 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
Topic: test Partition: 2 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1
[nutanix@nkp-boot ~]$ kubectl exec -n kafka kafka-0 -- bash -c 'echo "hello kafka" | kafka-console-producer --bootstrap-server localhost:9092 --topic test'
Defaulted container "kafka" out of: kafka, fix-permissions (init)
[nutanix@nkp-boot ~]$ kubectl exec -n kafka kafka-0 -- kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning --max-messages 1
Defaulted container "kafka" out of: kafka, fix-permissions (init)
hello kafka
Processed a total of 1 messages

รายการ access url

kafka (3 brokers)10.55.39.62:9092
Schema Registry (2 replicas)Internal schema-registry:8081
Control Centerhttp://10.55.39.63:9021

เข้าหน้าจอ Control Center UI, http://10.55.39.63:9021

ทดสอบใช้งานผ่าน app ที่เขียนด้วย react โดยดูตัวอย่าง code ได้จาก repo https://github.com/pkhamdee/kafka-test-app.git

ทำการ pull code และ deploy container ไปยัง kubernetes เพื่อทดสอบ kafka

[nutanix@nkp-boot ~]$ git clone https://github.com/pkhamdee/kafka-test-app.git
Cloning into 'kafka-test-app'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 12 (delta 1), reused 12 (delta 1), pack-reused 0 (from 0)
Receiving objects: 100% (12/12), 8.24 KiB | 1.18 MiB/s, done.
Resolving deltas: 100% (1/1), done.
[nutanix@nkp-boot kafka-test-app]$ ls
Dockerfile build-and-deploy.sh k8s-deployment.yaml package.json public server.js
[nutanix@nkp-boot kafka-test-app]$ kubectl apply -f k8s-deployment.yaml
deployment.apps/kafka-test-app created
service/kafka-test-app created
[nutanix@nkp-boot kafka-test-app]$ kubectl get pod -n kafka -l app=kafka-test-app
NAME READY STATUS RESTARTS AGE
kafka-test-app-58645c546b-tz2jq 1/1 Running 0 24s
[nutanix@nkp-boot kafka-test-app]$ kubectl get svc -n kafka
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kafka-test-app LoadBalancer 10.108.213.166 10.55.39.64 80:32485/TCP 11m

ทดสอบใช้งานผ่านหน้า GUI โดยเข้าไปที่ http://10.55.39.64

Integrate HashiCorp Vault with Kubernetes deployment

โดยหลักๆ แล้วจะมีวิธีการ integrate อยู่สองแบบคือ

  1. Vault Agent Injector เป็นวิธีการที่นิยมที่สุดด้วยการใช้ Annottions เพื่อให้ Vault Agent สร้าง sidecar container ทำการ inject credential ให้กับ pod
  2. Vault CSI Provider ใช้ Secret Store CSI Driver เพื่อ mount Vault secret ในรูปแบบ volume ให้กับ pod

ตัวอย่างนี้จะใช้วิธีแรกคือ Vault Agent Injector ที่เราได้ทำการ setup แล้วใน Install Hashicorp Vault to Kubernetes และใช้ตัวอย่างของ application testapp-cd โดยที่ branch main จะใช้ password จาก secret

เราจะเปลี่ยนให้เรียก password จาก vault ซึ่งต้องเพิ่ม annotation ตามตัวอย่างใน branch dev testapp-cd

เริ่มแรกต้องสร้าง service account สำหรับ vault agent เรียกไปยัง vault server

kubectl create sa vault-auth -n vault

กำหนดสิทธิให้กับ service account ด้วย cluster rolebinding เพื่อให้ review token ได้

kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-auth-tokenreview-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: vault
EOF

สร้าง service account ภายใต้ namespace ของ application

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: octopus-app-sa
namespace: default
EOF

ทำการ enable kubernetes auth method จาก vault cli

export VAULT_ADDR="http://10.55.39.59:8200"
[nutanix@nkp-boot ~]$ vault login hvs.j5wj9UsLG6lQqNS3nsdyBZN6
[nutanix@nkp-boot ~]$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

สร้างข้อมูลที่จำเป็นสำหรับ kubernetes auth method

K8S_HOST=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
K8S_CA_CERT=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d)
TOKEN_REVIEWER_JWT=$(kubectl create token vault-auth -n vault 2>/dev/null || \
kubectl get secret $(kubectl get sa vault-auth -n vault -o jsonpath='{.secrets[0].name}') -n vault -o jsonpath='{.data.token}' | base64 -d)

ทำการ configure kubernetes auth method

vault write auth/kubernetes/config \
kubernetes_host="${K8S_HOST}" \
kubernetes_ca_cert="${K8S_CA_CERT}" \
token_reviewer_jwt="${TOKEN_REVIEWER_JWT}"

สร้าง read policy ให้กับ application ให้ read ที่ secret path ได้

vault policy write octopus-app-policy - <<POLICY
path "secret/data/octopus/mongodb" {
capabilities = ["read"]
}
POLICY

สร้าง vault role binding ให้กับ service account ของ application และ policy ที่สร้างขึ้น

vault write auth/kubernetes/role/octopus-app \
bound_service_account_names=octopus-app-sa \
bound_service_account_namespaces=default \
policies=octopus-app-policy \
ttl=1h

สร้าง secret บน vault server

vault kv put secret/octopus/mongodb \
DB_USER="admin" \
DB_PASSWORD="password123"

ทำสอบ deploy app โดย clone code จาก testapp-cd แล้วเปลี่ยนไปที่ branch dev

[nutanix@nkp-boot ~]$ git clone https://gitlab.com/pkhamdee/testapp-cd.git
Cloning into 'testapp-cd'...
remote: Enumerating objects: 189, done.
remote: Counting objects: 100% (189/189), done.
remote: Compressing objects: 100% (86/86), done.
remote: Total 189 (delta 97), reused 179 (delta 93), pack-reused 0 (from 0)
Receiving objects: 100% (189/189), 21.81 KiB | 21.81 MiB/s, done.
Resolving deltas: 100% (97/97), done.
[nutanix@nkp-boot ~]$ cd testapp-cd/
[nutanix@nkp-boot testapp-cd]$ git checkout dev
branch 'dev' set up to track 'origin/dev'.
Switched to a new branch 'dev'
[nutanix@nkp-boot testapp-cd]$ cd k8s-yamls
[nutanix@nkp-boot k8s-yamls]$ ls
app-deployment.yaml app-service.yaml argocd configmap.yaml mongodb-deployment.yaml mongodb-service.yaml route.yaml secret.yaml
[nutanix@nkp-boot k8s-yamls]$ k apply -f .
deployment.apps/octopus-app created
service/octopus-app created
configmap/octopus-exam-config created
deployment.apps/mongodb created
service/mongodb created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/octopus-app-route created
secret/octopusexam-secret created

ตรวจสอบ vault agent ว่าทำงานถูกต้องจากการ describe ที่ pod

[nutanix@nkp-boot ~]$ kubectl describe pod octopus-app-6dcf45487d-dnpbs
Name: octopus-app-6dcf45487d-dnpbs
Namespace: default
Priority: 0
Service Account: octopus-app-sa
Node: workload01-md-0-s5zcv-ntnvg-xrxpm/10.55.39.109
Start Time: Fri, 27 Feb 2026 06:15:57 +0000
Labels: app=octopus-app
pod-template-hash=6dcf45487d
Annotations: vault.hashicorp.com/agent-inject: true
vault.hashicorp.com/agent-inject-secret-db-creds: secret/data/octopus/mongodb
vault.hashicorp.com/agent-inject-status: injected
vault.hashicorp.com/agent-inject-template-db-creds:
{{- with secret "secret/data/octopus/mongodb" -}}
export DB_USER="{{ .Data.data.DB_USER }}"
export DB_PASSWORD="{{ .Data.data.DB_PASSWORD }}"
{{- end -}}
vault.hashicorp.com/role: octopus-app
Status: Running
IP: 192.168.1.45
IPs:
IP: 192.168.1.45
Controlled By: ReplicaSet/octopus-app-6dcf45487d
Init Containers:
wait-for-mongodb:
Container ID: containerd://0dc59ec4b9b7894eedbbe48ed3a6ec3bff7dcd75b5408720fb0c89bd9d5abc7f
Image: busybox:1.35
Image ID: docker.io/library/busybox@sha256:98ad9d1a2be345201bb0709b0d38655eb1b370145c7d94ca1fe9c421f76e245a
Port: <none>
Host Port: <none>
Command:
sh
-c
until nc -z mongodb 27017; do echo waiting for mongodb; sleep 2; done;
State: Terminated
Reason: Completed
Exit Code: 0
Started: Fri, 27 Feb 2026 06:15:58 +0000
Finished: Fri, 27 Feb 2026 06:16:21 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gpwm5 (ro)
/vault/secrets from vault-secrets (rw)
vault-agent-init:
Container ID: containerd://3c34e6174111c0cd278f1a251c48c01a339e625982c18af05b6fa91040eb9107
Image: hashicorp/vault:1.21.2
Image ID: docker.io/hashicorp/vault@sha256:eb0ba6836e8d4699b7a1e8ca70d8433f7b87dcd067e6d82dff237d3ed2600ea0
Port: <none>
Host Port: <none>
Command:
/bin/sh
-ec
Args:
echo ${VAULT_CONFIG?} | base64 -d > /home/vault/config.json && vault agent -config=/home/vault/config.json
State: Terminated
Reason: Completed
Exit Code: 0
Started: Fri, 27 Feb 2026 06:16:22 +0000
Finished: Fri, 27 Feb 2026 06:16:22 +0000
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 250m
memory: 64Mi
Environment:
NAMESPACE: default (v1:metadata.namespace)
HOST_IP: (v1:status.hostIP)
POD_IP: (v1:status.podIP)
VAULT_LOG_LEVEL: info
VAULT_LOG_FORMAT: standard
VAULT_CONFIG: eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6ImF1dGgva3ViZXJuZXRlcyIsImNvbmZpZyI6eyJyb2xlIjoib2N0b3B1cy1hcHAiLCJ0b2tlbl9wYXRoIjoiL3Zhci9ydW4vc2VjcmV0cy9rdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6dHJ1ZSwicGlkX2ZpbGUiOiIvaG9tZS92YXVsdC8ucGlkIiwidmF1bHQiOnsiYWRkcmVzcyI6Imh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMCJ9LCJ0ZW1wbGF0ZSI6W3siZGVzdGluYXRpb24iOiIvdmF1bHQvc2VjcmV0cy9kYi1jcmVkcyIsImNvbnRlbnRzIjoie3stIHdpdGggc2VjcmV0IFwic2VjcmV0L2RhdGEvb2N0b3B1cy9tb25nb2RiXCIgLX19XG5leHBvcnQgREJfVVNFUj1cInt7IC5EYXRhLmRhdGEuREJfVVNFUiB9fVwiXG5leHBvcnQgREJfUEFTU1dPUkQ9XCJ7eyAuRGF0YS5kYXRhLkRCX1BBU1NXT1JEIH19XCJcbnt7LSBlbmQgLX19XG4iLCJsZWZ0X2RlbGltaXRlciI6Int7IiwicmlnaHRfZGVsaW1pdGVyIjoifX0ifV0sInRlbXBsYXRlX2NvbmZpZyI6eyJleGl0X29uX3JldHJ5X2ZhaWx1cmUiOnRydWV9fQ==
Mounts:
/home/vault from home-init (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gpwm5 (ro)
/vault/secrets from vault-secrets (rw)
Containers:
octopus-nodejs-container:
Container ID: containerd://214f0a43c0b1f52b8972fe5a7ef80bb63d4cff6bf79a95df609042c1a453d8aa
Image: pkhamdee/testapp:c0d742f
Image ID: docker.io/pkhamdee/testapp@sha256:f4d4a5ef8a19853ceef1be3ecec90643bd74ace7b9f0b04c0f22ac2a1a5a1624
Port: 3000/TCP
Host Port: 0/TCP
Command:
/bin/sh
-c
Args:
source /vault/secrets/db-creds && node app.js
State: Running
Started: Fri, 27 Feb 2026 06:16:28 +0000
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 256Mi
Requests:
cpu: 500m
memory: 256Mi
Environment:
DB_HOST: <set to the key 'DB_HOST' of config map 'octopus-exam-config'> Optional: false
DB_PORT: <set to the key 'DB_PORT' of config map 'octopus-exam-config'> Optional: false
DB_NAME: <set to the key 'DB_NAME' of config map 'octopus-exam-config'> Optional: false
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gpwm5 (ro)
/vault/secrets from vault-secrets (rw)
vault-agent:
Container ID: containerd://2d68c01152a9fcd0c98ac6322209f6eb578b3cb4343861664a0a2da7be720320
Image: hashicorp/vault:1.21.2
Image ID: docker.io/hashicorp/vault@sha256:eb0ba6836e8d4699b7a1e8ca70d8433f7b87dcd067e6d82dff237d3ed2600ea0
Port: <none>
Host Port: <none>
Command:
/bin/sh
-ec
Args:
echo ${VAULT_CONFIG?} | base64 -d > /home/vault/config.json && vault agent -config=/home/vault/config.json
State: Running
Started: Fri, 27 Feb 2026 06:16:28 +0000
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 250m
memory: 64Mi
Environment:
NAMESPACE: default (v1:metadata.namespace)
HOST_IP: (v1:status.hostIP)
POD_IP: (v1:status.podIP)
VAULT_LOG_LEVEL: info
VAULT_LOG_FORMAT: standard
VAULT_CONFIG: eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6ImF1dGgva3ViZXJuZXRlcyIsImNvbmZpZyI6eyJyb2xlIjoib2N0b3B1cy1hcHAiLCJ0b2tlbl9wYXRoIjoiL3Zhci9ydW4vc2VjcmV0cy9rdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6ZmFsc2UsInBpZF9maWxlIjoiL2hvbWUvdmF1bHQvLnBpZCIsInZhdWx0Ijp7ImFkZHJlc3MiOiJodHRwOi8vdmF1bHQudmF1bHQuc3ZjOjgyMDAifSwidGVtcGxhdGUiOlt7ImRlc3RpbmF0aW9uIjoiL3ZhdWx0L3NlY3JldHMvZGItY3JlZHMiLCJjb250ZW50cyI6Int7LSB3aXRoIHNlY3JldCBcInNlY3JldC9kYXRhL29jdG9wdXMvbW9uZ29kYlwiIC19fVxuZXhwb3J0IERCX1VTRVI9XCJ7eyAuRGF0YS5kYXRhLkRCX1VTRVIgfX1cIlxuZXhwb3J0IERCX1BBU1NXT1JEPVwie3sgLkRhdGEuZGF0YS5EQl9QQVNTV09SRCB9fVwiXG57ey0gZW5kIC19fVxuIiwibGVmdF9kZWxpbWl0ZXIiOiJ7eyIsInJpZ2h0X2RlbGltaXRlciI6In19In1dLCJ0ZW1wbGF0ZV9jb25maWciOnsiZXhpdF9vbl9yZXRyeV9mYWlsdXJlIjp0cnVlfX0=
Mounts:
/home/vault from home-sidecar (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gpwm5 (ro)
/vault/secrets from vault-secrets (rw)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-gpwm5:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
home-init:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium: Memory
SizeLimit: <unset>
home-sidecar:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium: Memory
SizeLimit: <unset>
vault-secrets:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium: Memory
SizeLimit: <unset>
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 43m default-scheduler Successfully assigned default/octopus-app-6dcf45487d-dnpbs to workload01-md-0-s5zcv-ntnvg-xrxpm
Normal Pulling 43m kubelet Pulling image "busybox:1.35"
Normal Pulled 43m kubelet Successfully pulled image "busybox:1.35" in 1.178s (1.178s including waiting). Image size: 2159953 bytes.
Normal Created 43m kubelet Created container: wait-for-mongodb
Normal Started 43m kubelet Started container wait-for-mongodb
Normal Pulled 42m kubelet Container image "hashicorp/vault:1.21.2" already present on machine
Normal Created 42m kubelet Created container: vault-agent-init
Normal Started 42m kubelet Started container vault-agent-init
Normal Pulling 42m kubelet Pulling image "pkhamdee/testapp:c0d742f"
Normal Pulled 42m kubelet Successfully pulled image "pkhamdee/testapp:c0d742f" in 5.156s (5.156s including waiting). Image size: 55585478 bytes.
Normal Created 42m kubelet Created container: octopus-nodejs-container
Normal Started 42m kubelet Started container octopus-nodejs-container
Normal Pulled 42m kubelet Container image "hashicorp/vault:1.21.2" already present on machine
Normal Created 42m kubelet Created container: vault-agent
Normal Started 42m kubelet Started container vault-agent

DevOps Framework

กระบวนการ Deliver Software จาก Ideas จนถึงใช้งานบน Production ต้องผ่าน process อะไรบ้างเมื่อพิจารณากระบวนการ DevOps เข้ามาเกี่ยวข้อง สามารถสรุปเป็นแนวทางได้ดังนี้

เราควรมี Product Team ที่ประกอบด้วยผู้ที่เกี่ยวของกับการพัฒนา Product เช่น Product Owner, Designer และ Developer ทั้งนี้ทีมควรพิจารณาเรื่อง Balance Team ที่ต้องมีผู้ที่เกี่ยวข้องกับการพัฒนาและส่งมอบ Product อยู่ในทีม เพื่อให้การดำเนินการใดๆ สามารถทำได้เลย ไม่ต้องเสียเวลาในการขอความร่วมมือใดๆ ข้าม Team เช่นการมี Security และ Operation อยู่ใน Team ด้วย จะทำให้ทุกคนเข้าใจใน Product ที่กำลังพัฒนา การตัดสินใจ การร่วมไม้ร่วมมือตั้งแต่ต้น และตลอดการพัฒนา โดยใช้หลักการ Why (ทำไมเราต้องพัฒนา) What (ส่ิงที่ต้องพัฒนาคืออะไร) How (จะพัฒนาขึ้นมาได้ยังไง) เพื่อส่งเสริมความเข้าใจ และทำงานร่วมกันภายในทีม

เครื่องมือที่เกี่ยวข้อง

  • Portfolio Management
  • Application Lifecycle Management
  • Team Collaboration and Work Tracking (Jira)
  • Security Architecture and Design
  • Threat Modeling
  • Knowledge Sharing (Confluence)

สิ่งที่เป็น Output ของ Team คือ Source Code รวมถึง Application Code, Testing Code และ Infra Code จะถูกจัดการด้วย Code Repository (GitLab, GitHub, GitBucket) เพื่อให้สามารถจัดการเรื่อง Versioning และการ Peer Review กับผู้ที่เกี่ยวข้องได้ และต้องมีการจัดการเรื่อง Quality ของ Code เช่น จำนวน Test Coverage การ Follow ตาม Practice ของการเขียน Code เช่น Code Quality/Secure Coding Standards (SonarQube, Snyk) และมีการ Integrate Security Tool เพื่อตรวจสอบช่องโหว่ด้านความปลอดภัยในระดับ Code เช่น Open Source Security & License Tracking (BlackDuck, Snyk), Static Application Security Testing – SAST (Coverity, Fortify, SonarQube, Checkmarx, Veracode) เป็นต้น

เมื่อ Code ผ่านกระบวนการตรวจสอบแล้ว กระบวนการต่อไปคือการ Build (maven, npm, MSBuild) โดยจะได้ Binary File ที่สามารถทำงานได้ (Working Software) ทั้งนี้ขั้นตอนการ Build จะมีการเรียก Unit Test (junit, Nunit) และ Functional Test (Cucumber) หลังจากนั้นก็จะจัดเก็บ Binary File ไว้ใน Application Repository (jFrog, Nexus)

ถ้า Software จะต้องทำงานบน Container Platform ก็ต้อง Pack Binary File ไว้ใน Container Format (Containerize) ด้วยเครื่องมือ Container Build (Docker file, Buildpack) และจัดเก็บไว้ใน Container Registry (Habor, jForg, DockerHub) หลังจากนั้นจึงจะทำการสร้าง SBOM (Software Bill of Material) เพื่อเป็น metadata file อธิบายถึง ข้อมูล Components ต่างๆ ที่ใช้ในการสร้าง Software ขึ้นมา แล้วต่อด้วย Software Composite Analysis – SCA (Trivy, BlackDuck, Snyk, jFrog X-Ray) เพื่อหาช่องโหว่ด้านความปลอดภัยของ software components ต่างๆ ที่นำมาใช้

จากนั้นจะต้องมีการออก Release Number ของ Software เพื่อเป็น Reference ในระบบ Release Management ที่ประกอบด้วย Features หรือ Update ต่างๆ ของ Software Release และ Update ไปยังระบบ Work Tracking และทำการ Tag Release Version ใน Code Repository

หลังจากกระบวนการออก Release Version ของ Software เสร็จจะขึ้นอยู่กับการตกลงของทีม Delivery และ QA ที่จะนำ Software Release เข้าสู่กระบวนการทดสอบโดยทีม QA และ Security ที่ครอบคลุมทั้งที่เป็น Functional และ Non-Function Test โดยมีเครื่องมือ Quality Management และ Update ไปยัง Work Tracking

เครื่องมือที่ QA ใช้ในการทดสอบ Software เช่น

  • Katalon Studio
  • Selenium
  • Appium
  • Applitools
  • Blazemeter
  • Saucelabs
  • Smartbear
  • Wiremock

เครื่องมือสำหรับ Dynamic Application Security Test เช่น

  • Fortify
  • Aqua Sec
  • OWASP ZAP
  • Tanable Web App Scan

เครื่องมือในการทำ performance test เช่น

  • JMeter
  • Smartbear
  • Postman
  • Locust

การจะนำ software ที่ผ่านการทดสอบแล้วขึ้น production อาจจะมีกระบวนการ approval process และทำการ deploy ด้วยวิธีการที่เหมาะสมเช่น blue-green, cannary  หรือ a/b testing หลังจากนั้นอาจจะมีเครื่องมือสำหรับตรวจสอบความปลอดภัย เช่น Infra Valnerabilities (Tenable Nessus), Interactive application security testing – IAST (AquaSec, Twistlock, Synopsys Seeker) และ Chaos Testing (Chaos Monkey, Chaos Mash) เพื่อให้มั่นใจว่าระบบจะสามารถทำงานได้ในสถานการณ์ต่างๆ เช่น ช่วง peak time และบางส่วนของระบบมีปัญหา

สิ่งที่สำคัญที่ขาดไม่ได้คือ log, monitoring และเก็บข้อมูลสถิติต่างๆ (Metric Collection – Prometheus, InfluxDB) เพื่อนำมาใช้ในการปรับปรุงระบบ ให้ดีขึ้นเรื่อยๆ ด้วยการ Update ข้อมูลกลับไปยัง Product Team พิจารณาแก้ไขให้ดีขึ้น

เครื่องมือสำหรับ Log และ Monitoring เช่น

  • Datadog
  • Grafana
  • Appdynamic
  • Dynatrace
  • Splunk
  • Solarwind
  • Elastic Stack (EFK, ELK)

What is DevOps

ในความเข้าใจเกี่ยวกับ devops นั้นแตกต่างตามบริบทการใช้และการเรียกของแต่ละคน จึงไม่ได้เป็นประเด็นมากนักว่า devops นั้นมีความเข้าใจถูกหรือผิด เพราะคนใช้มีบริบทที่แตกต่างกัน ลองดูบริบทในมุมของ ความเป็นมาและทำไมต้องมี

devops เกิดขึ้นจากปัจจุบันที่แต่ละทีม มีการแข่งขันกันในการสร้าง Innovation และนำ technology ใหม่ๆ มาใช้เพื่อปรับปรุงการทำงาน และสร้าง service ต่างๆ ออกมาให้ตรงใจและพึงพอใจต่อผู้ใช้งาน

Development team มีการใช้ technology container ในการ package application ด้วยการ pack ความต้องการที่ application ต้องใช้ในการทำงานไว้ใน package เดียวทั้งที่เป็น OS, application dependency, lib และ configuration ที่จำเป็น ทำให้ container สามารถทำงานในสภาพแวดล้อมต่างๆ ได้ง่าย และการทำงานได้ผลลัพธ์ไม่แตกต่างๆกัน

ขณะเดียวกัน Operation team ก็ศึกษา Kubernetes เพื่อให้เป็น platform สำหรับ application team สามารถเรียกใช้ผ่าน APIs ในการ deploy และทดสอบ application โดยที่ไม่ต้องเสียเวลาในการขอบริการ Virtual Machine, Network, Storage ทำให้ประหยัดเวลาในการให้บริการกับทีม Developer (Kubernetes = Infra APIs)

จะเห็นว่าแต่ละทีมต่างก็เสนอ Innovation ใหม่ๆ สิ่งที่เกิดขึ้นคือเกิดการพูดคุย ร่วมด้วยช่วยกันมากขึ้นระหว่างทีม Development และ Operation เกิดเป็น Culture และมีเป้าหมายที่ชัดเจนในการที่จะทำยังไงเพื่อทีจะทำให้การสองมอบ application และบริการถึงมือผู้ใช้ได้ดีที่สุด

ดังนั้น DevOps จึงเป็น culture ไม่ใช่ tool หรือ technology

Culture

DevOps เกี่ยวข้องกับการทำลายอุปสรรคระหว่างทีม เวลาที่เสียไประหว่างที่รอเพื่อให้ได้ resource ตามความต้องการ เอกสารและ process ที่ทำให้กระบวนการส่งมอบ software ไม่มีประสิทธิภาพ การที่ทีม Dev และ Operation มีการพูดคุยเพื่อทบทวนกระบวนการ และหา tool ที่สามารถลดขั้นตอน รวมทั้งลดอุปสรรค์ระหว่างการส่งมอบ software ให้น้อยที่สุด จะส่งผลดีโดยรวมต่อระบบ IT

Automation

เพื่อให้บรรลุเป้าหมาย การใช้ระบบอัตโนมัตินอกจากช่วยประหยัดเวลาแล้วยังป้องกันข้อบกพร่อง สร้างความสม่ำเสมอ รวมถึงการส่งเสริมการให้บริการในรูปแบบ self-service ในทีม

Measurement

เป้าหมายของทีมคือการปรับปรุงกระบวนการอย่างต่อนเนื่อง ดังนั้นจำเป็นต้องมีการวัดผล หรือ dashbarod ที่แสดงสถานะการทำงานปัจจุบัน จึงจะสามารถนำข้อมูลมาใช้ในการปรับปรุงระบบให้ดีอยู่เสมอได้

Sharing

กุญแจสำคัญของความสำเร็จของ DevOps ในทุกองค์กรคือการแบ่งปันเครื่องมือ ความรู้ บทเรียนที่ค้นพบ เพื่อให้ทีมได้รับข้อมูลเกี่ยวกับวิธีการใหม่ๆ สร้างสรรค์สิ่งใหม่ๆ สร้างการเรียนรู้ในทีมร่วมกัน

ทุกองค์กร จำเป็นต้องมี DevOps การจะพัฒนา culture นี้ขึ้นมา จะช้าหรือเร็วนั้นขึ้นอยู่กับวัฒนธรรมองค์กรนั้นๆ ว่าเอื้ออำนวยขนาดไหน ไม่ช้าก็เร็วองค์กรก็จะต้องมี DevOps เกิดขึ้น เหมือนถนนที่ใช้เดินทางไปยังกรุงโรม ที่เมื่อออกเดินทาง ปลายทางก็จะเป็นกรุงโรม

Note:

DevOps = Developer + Operation

DevSecOps = Developer + Security + Operation เป็น term ใหม่ ที่ทีม security เข้ามาร่วมด้วยในกระบวนการส่งมอบ software โดยการใช้ทักษะและ tool ที่ทีม security มีมาใช้เป็นส่วนหนึ่งในกระบวนการส่งมอบ โดยการตรวจสอบช่องโหว่ด้านความปลอดภัยตั้งแต่เริ่มต้น develop software (shift-left security)

FinOps = Financial + Operation เป็นอีก term ที่ทีม financial ที่เป็นผู้ support เรื่อง budget ต่างๆ สำหรับทีม operation เพื่อใช้ manage application อยากรู้ข้อมูลเรื่องค่าใช้จ่าย และความสมเหตุสมผลการการลงทุน การมีข้อมูลที่ monitor เรื่อง IT spending อยู่ตลอดเวลาทำให้ทีม Finance สามารถ plan เรื่อง budget และควบคุมค่าใช้จ่ายได้ ขณะเดียวกัน operation team ก็สามารถใช้ข้อมูลนี้ในการ optimize resource ที่ใช้สำหรับ run application ได้เช่นเดียวกัน

Engineering Standard for DevOps

สรุปมาตรฐานในการพัฒนา DevOps ในองค์กร เพื่อเป็นหลักพิจารณาในการเริ่มต้นสร้างวัฒนธรรม (culture) กระบวนการ (process) และเลือก technology ที่เหมาะสม โดยสรุปเป็นหลักการได้ดังนี้

12 Factors

Application ต้องถูกสร้างตามหลักการ 12 Factors ซึ่งเป็นหลักการในการพัฒนา cloud native application ที่สามารถทำงานในสถาพแวดล้อมที่แตกต่างๆ กันได้ง่าย โดยมีหลักการเช่น ไม่เขียน log ลง file โดยให้ผ่าน standard output สร้าง environment ที่มี component, version และ configuration เหมือนกัน (dev, sit, prod) จัดการ configuration แยกจาก application ผ่านทาง environment เป็นต้น

Git Pre-Commit Hooks

ต้องมั่นใจว่ามีการใช้ pre commit hooks ของ GIT เพื่อ validate code ก่อนที่ code จะถูกจัดเก็บใว้ใน repository การทำ hook คือทุกครั้งที่ engineer ทำการ push code ไปยัง source control นั้น code จะถูกตรวจสอบและทำการ test ก่อน เพื่อป้องกัน code ที่ไม่สมบูรณ์​​ (poor quality code) ถูกเขียนลง repository การที่มี process นี้ก่อนกระบวนการ commit จะทำให้มั่นใจได้ว่า code ที่จัดเก็บจะเป็น code ที่มีคุณภาพเสมอ แนะนำตรวจสอบตามรายการดังนี้

  • Linting เป็นการตรวจสอบ code ว่าตรงตาม coding standards หรือไม่ เช่น การตั้งชื่อ function หรือ variable การใช้ space การเขียน comment
  • Unit Tests ทำการ execute test case
  • Commit Message เช่น commit message จะต้องมีอย่างน้อย 80 ตัวอักษร เพื่อเป็นการบังคับให้ engineer สื่อความหมายของ code ที่จะเก็บใน repository ได้จัดเจน
  • Jira มีการ reference ส่วนของ code กับ ticket ใน Jira เพื่อให้สามารถ trace ได้ว่า code ชุดนี้เกี่ยวข้องกับ requirement อะไร

Source Code Management

Source code จะต้องถูกจัดการผ่าน Gitlab หรือ git version control รวมถึง Infra code หรือ technical code อื่นๆ เป็นการจัดการ code ที่เดียวเพื่อความชื่อถือและถูกต้องเสมอสำหรับ development team (one source of truth) โดยมีหลักพิจารณาคือ

  • Trunk Based Development เป็นการให้ engineer ทำงานอยู่บน development branch (main branch) ในระยะเวลาที่สั้น (short iterations) ดีกว่าให้มีหลายๆ release หรือ feature branch เพื่อลดปัญหาเรื่องความซับซ้อนและยากลำบากในการ merge code (merge conflicts)
  • Feature Toggles สำหรับ code ที่ยังใช้งานไม่ได้ หรือพัฒนายังไม่เสร็จจะต้องถูกซ่อนไว้ โดยใช้ feature toggle เพื่อป้องกันหรืออนุญาติให้มีการนำ code ไปใช้
  • Configuration in Environment ไม่เก็บ configuration ต่างๆ ใว้ใน source code เพื่อป้องกันไม่ให้ส่วนที่เป็น credential หรือข้อมูลสำคัญรั่วไหล อีกทั้งยังเป็นการทำให้ code ไม่มี dependency เรื่อง config สามารถจัดการ config ผ่านทาง environment ที่ง่ายกว่าและย้ายไปยัง environment ได้สะดวกด้วย

Continuous Integration and Delivery (CI/CD)

ทุกๆ commit ใน git จะมีการ start CD/CD pipeline เพื่อมั่นใจว่าทุก code จะมีการ run ขั้นตอนต่างๆ ที่เป็นมาตรฐานในการส่งมอบ Application ขององค์กร

  • Validate code standard อย่างเช่น space และ brackets ที่อ่านง่าย
  • Execute test case
  • Code quality metric จะต้องมั่นใจว่า code ผ่านมาตรฐานที่กำหนดไว้ เช่น ไม่มีช่องโหว่ด้านความปลอดภัย (vulnerabilities) จำนวน coverage unit จะต้องมีมากกว่า 80% จำนวน smells bug หรือ code ที่เขียนโดยไม่เป็นไปตาม practices
  • Automate end-to-end functional และ non-functional testing
  • Deploy ไปยัง environment ต่างๆ ด้วยรูปแบบที่เหมาะสมเพื่อไม่ให้กระทบกับการทำงาน เช่น bluegreen, canary

Automated Testing

ทำการ automate test ในทุกระดับทั้งที่เป็น functional และ non-functional โดยเลือกใช้ tool ที่เหมาะสมในแต่ละ test case การใช้ automate test tool จะทำให้เห็นปัญหาก่อน User ประกอบด้วย

  • Unit เป็นการทดสอบส่วนไดๆ ใน code โดยต้องมี coverage ในการ test ที่เหมาะสม
  • Integration เป็นการทำ end to end test ประกอบด้วยการทำ API test และ UI test เพื่อ simulate การใช้งานของ User
  • Security ประกอบด้วย static และ dynamic security testing เป็น simulate การโจมตี application เพื่อหาช่องโหว่ด้านความปลอดภัย (vulnerabilities)
  • Performance เป็นการ load test เพื่อให้มั่นใจว่า application สามารถทำงานได้ในภาวะการณ์ต่างๆ ได้ ทั้งกรณีที่มี request และมีการส่ง data (payload) เข้ามามากกว่าปกติ

Security Testing

Web app และ Mobile application จำเป็นต้องมีการตรวจสอบความปลอดภัยมากกว่าเดิม จากการเข้าถึงของผู้ใช้งานได้ทั่วไป ดังนั้นโดยทั่วไปจึงมีการทำ hardening และ end-to-end penetration test จากหน่วยงานภายนอกก่อนที่จะขึ้น production การทำ security testing สามารถทำได้หลายวิธี โดยการทำให้เป็นส่วนหนึ่งของ agile process และ devops

  • Dynamic Application Security Testing (DAST) และ Static Application Security Testing (SAST) เป็นเครื่องมือและวิธีการที่อยู่ในกระบวนการ Continuous Integration pipeline เพื่อให้มั่นใจว่าทุกขั้นตอนในการบวนการ code change build และ release จะมีการตรวจสอบ security อยู่เสมอ (shift-left security)
  • สำหรับการตรวจสอบจากองค์กรภายนอก (Third-Party Penetration) สามารถทำในบางจุดที่สำคัญเพื่อให้มีความคล่องตัว ลดเวลาและค่าใช้จ่ายในการตรวจสอบความปลอดภัยของ application ได้ โดยที่ไม่เป็นการเพิ่มความเสี่ยง

Dynamic App Security Testing – DAST

สามารถใช้ OWASP ZAP เพื่อ scan application ที่ทำงานอยู่ได้ (black box testing) โดยจะทดสอบความปลอดภัยด้วย test case พื้นฐานสำหรับ web application ที่เป็นมาตรฐานคือ OWASP Top 10 Vulnerabilities

ZAP สามารถเป็นขั้นตอนหนึ่งใน Continuous Integration Pipeline เพื่อให้ scan โดยอัตโนมัติเมื่อ code change, build และ release รวมถึงรายงานช่องโหว่ด้านความปลอดภัย (vulnerabilities) ให้กับทีม developer ทราบทันที ป้องกันไม่ให้ release application ที่ไม่ปลอดภัยไปยัง production

Static App Security Testing – SAST

ใช้ SonarQube เพื่อวิเคราะห์ source code และตรวจจับ code ที่อาจจะเป็นช่องโหว่ด้านความปลอดภัยของ application โดยให้ SonarQube เป็นส่วนหนึ่งของ Continuous Integration Pipeline จะทำให้มีความมั่นใจ และป้องกัน application ที่ไม่ปลอดภัยถูกใช้บน production

Independent App Penetration Testing

กระบวนการพัฒนา software ในแบบ waterfall project ส่วนใหญ่จะใช้ องค์กรอิสระที่เชื่อถือได้เพื่อตรวจสอบช่องโหว่ด้านความปลอดภัยของ software ก่อนขึ้น production ซึ่งส่งผลกระทบกับ cost ระยะเวลา และความถี่ในการ release software ทางเลือกที่เหมาะสมที่สามารถเข้าได้กับกระบวนการ agile และ devops คือรวมวิธีการของ DAST และ SAST เข้ามาใน process ก็จะทำให้ลดจำนวนที่ต้องใช้องค์การภายนอกได้ โดยมีหลักการพิจารณาคือ

  • Schedule appropriately กำหนดกรอบระยะเวลาที่ชัดเจนในการทำ penetration test หลังจากที่ทุก function มีการ implement เรียบร้อย
  • Lead time กำหนดให้ penetration test ทำในรอบ sprint ทำให้สามารถแก้ไขควบคู่กับการทดสอบ เพื่อไม่ให้ส่งผลกระทบกับการ release application
  • Cost สำหรับการทำ penetration test จะลดลงเมื่อมีการทำ penetration test แค่ครั้งเดียวก่อนที่จะ release application โดยที่ไม่พบช่องโหว่ด้านความปลอดภัย

OWASP Dependency-Check

เป็นการทำ Software composition analysis ด้วยการตรวจสอบข้อมูลของ File (file name, POM files, ZIP files, native libraries, .NET assemblies, package name ..) แล้วตรวจสอบกับฐานข้อมูล National Vulnerability Database – NVD เพื่อแจ้งเตือนถึงความเสี่ยงในการใช้งาน package หรือ library ใน source code ปัจจุบัน OWASP Dependency-Check สามารถใช้ร่วมกับหลายๆ tool เช่น

  • Ant Task
  • Command line tool – Windows, *nix
  • Gradle plugin
  • Jenkins plugin
  • Maven plugin

Semantic Versioning

ใช้ Semantic versioning เพื่อจัดการ version ของ release สำหรับหลาย ๆ microservice และ application โดยการ tag version number ที่ release branch ที่สร้างใน git repository

version ของ release ใช้หลักการที่ง่ายเพื่อให้ผู้ใช้ microservice หรือ application เข้าใจ change ที่เกิดขึ้น รวมถึงทีม tester การจัดการ version สามารถใช้หลักการดังต่อไปนี้

  • Major เป็นการเปลี่ยนแปลงที่สำคัญ หรือส่งผลกระทบต่อภายนอกเช่นการเปลี่ยนการเรียกใช้ API หรือ Interface
  • Minor การแก้ไขเพิ่มเติม function ที่มีอยู่แล้ว หรือเพิ่มความสามารถของ function เดิมให้สมบูรณ์และดียึ่งขึ้น
  • Patch เป็นการแก้ข้อผิดพลาดของ code (bug)
  • Chore เป็นการเปลี่ยนแปลงไดๆ ที่ไม่จำเป็นต้องเปลี่ยน version เช่นการทำ code refactoring

ข้อมูลเพิ่มเติมเกี่ยวกับ semantic version ได้จาก https://semver.org

Application & Platform Monitoring

Application ที่ทำงานบน environment มีการตรวจสอบสถานะการทำงานของ application และ platform และแจ้งเตือนเมื่อมีปัญหาไดๆ เกิดขึ้นแบบ realtime ด้วยการเก็บข้อมูลในรูปแบบ matrics อาจจะใช้ Prometheus และแสดงผลใน grafana dashboard เพื่อให้ทีมสามารถเห็นการทำงานของ application ที่เป็นปัจจุบันและสถิติที่ผ่านมาได้ ขอมูลจำเป็นที่ต้องจัดเก็บเช่น

  • Uptime/Downtime
  • CPU memory utilization
  • Failed network request 4xx 5xx response
  • Latency slow running request
  • Request Frequency (DDoS Protection)
  • Error message logged by application
  • Disk space usage

Collaboration

ช่องทางการติดต่อสือสารในทีม ทั้งที่เป็น internal และ external เป็นสิ่งสำคัญ และต้องใช้ tool ที่เหมาะสม เช่น

  • Slack ทีมสามารถ share ข้อมูลเกี่ยวกับ project พวก knowledge ต่างๆ ระหว่างทีม และสามารถใช้แจ้ง alert ได้กรณีที่ pipeline มีปัญหา อีกทั้งยัง invite business หรือ stakeholder เข้ามาในทีมด้วยเพื่อการรับรู้ข่าวสารต่างๆ ภายในทีม
  • Jira เป็น tool ที่ใช้ในการจัดการ product backlogs จัดการ sprint รวมถึง report ต่างๆ เพื่อให้ทีมเห็นถึง progress ในแต่ละงานได้

Cloud Platform

การทำ devops ให้สะดวกราบรื่นจำเป็นต้องมี platform ที่ช่วยอำนวยสะดวกในการ release, deploy, run และ manage application ได้ง่าย ปัจจุบันจะใช้ container technology ในการ packaging software และ run บน kubernetes environment เพราะสะดวกในการใช้งานเพราะการใช้งานจะผ่าน APIs ไม่มี manual process โดยใช้ Cloud Environment เป็น infrastructure เพื่อให้ platform สามารถ scale ได้ง่าย และ cloud environment ยังช่วยในเรื่องการจัดการ high availability , consistency และ resilent ของ application ได้ง่ายอีกด้วย