Kubernetes : Intergrate Vault secret for K8s Cluster.
Tạo và lưu secret trong cụm K8s luôn tiềm ẩn những rủi ro, vì thế, việc xây dựng 1 hệ thống lưu trữ secrets riêng là hoàn toàn phù hợp trong PROD.
VAULT chính là 1 lựa chọn hợp lý:
Mô hình này được áp dụng ở rất nhiều công ty lớn, mình từng làm bank như TCB, MSB, và cũng đang dùng mô hình tương tự.
Làm sao để các deployment có thể sử dụng đc secrets lưu tại Vault? Làm sao để tích hợp Vault với K8s?
Mình sẽ có 1 demo như sau :
- Vẫn là 1 app gen random data vào Mysql.
- Trong code, có chỉ định sẽ dùng env để chỉ định là sử dụng host, mysql password…
- Thay vì tạo secret như cách thông thường, thì ta sẽ lưu các secret vào Vault, và tích hợp với K8s.
Cách thực hiện :
- Triển khai Vault server 10.100.1.104
#Install vault
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault -y
- Tạo cấu hình single-node (Raft storage)
sudo mkdir -p /opt/vault/data
sudo chown -R vault:vault /opt/vault
sudo tee /etc/vault.d/vault.hcl >/dev/null <<'EOF'
ui = true
disable_mlock = true
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1 # Lab nhanh; Production nên dùng TLS (ghi chú ở cuối)
}
api_addr = "http://10.100.1.104:8200"
cluster_addr = "http://10.100.1.104:8201"
EOF
sudo systemctl enable vault
sudo systemctl start vault
sleep 30
sudo sysremctl status vault
- Khởi tạo & unseal
mkdir vault && cd vault
echo 'export VAULT_ADDR=http://10.100.1.104:8200' | sudo tee -a /etc/profile.d/vault.sh
source /etc/profile.d/vault.sh
vault operator init -key-shares=1 -key-threshold=1 > ~/vault/vault.init
grep 'Unseal Key 1' ~/vault/vault.init | awk '{print $4}' > ~/vault/unseal.key
grep 'Initial Root Token' ~/vault/vault.init | awk '{print $4}' > ~/vault/root.token
vault operator unseal "$(cat ~/vault/unseal.key)"
vault login "$(cat ~/vault/root.token)"
#Sau khi login, se hien thong tin dang nhap Vault qua token, vi du nhu sau :
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.20.2
Build Date 2025-08-05T19:05:39Z
Storage Type raft
Cluster Name vault-cluster-33700384
Cluster ID b5908982-4821-007d-1577-7c12aa2339d5
Removed From Cluster false
HA Enabled true
HA Cluster https://10.100.1.104:8201
HA Mode active
Active Since 2025-08-15T03:53:48.995498791Z
Raft Committed Index 37
Raft Applied Index 37
wasadm@databases-server:~/vault$ vault login "$(cat ~/vault/root.token)"
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.vXLK7LgQZuEgGVNbgMsfB2Q0
token_accessor d1nMP5A81wOvCJiJFV5P0UkR
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
Dùng token : hvs.vXLK7LgQZuEgGVNbgMsfB2Q0 để login vault
Url : http://10.100.1.104:8200
- Bật audit
sudo mkdir -p /var/log/vault && sudo chown vault:vault /var/log/vault
vault audit enable file file_path=/var/log/vault/audit.log
- Tạo KV v2
vault secrets enable -path=kv kv-v2
- Kết nối Vault ↔ Kubernetes (Kubernetes Auth)
Thực hiện trên con master : 10.100.1.120
mkdir rbac-vault && cd rbac-vault
Tạo ServiceAccount cho Vault dùng để review token:
vim vault-auth-sa.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-auth-delegator
roleRef:
kind: ClusterRole
name: system:auth-delegator
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: kube-system
---
kubectl apply -f vault-auth-sa.yaml
Lấy Token reviewer và CA của API server (Trên con master)
SA_TOKEN=$(kubectl create token vault-auth -n kube-system --duration=24h)
echo $SA_TOKEN
Copy SA_TOKEN sang Vault server :
export SA_TOKEN=gia tri cua SA_TOKEN
Copy content của ca.crt trong /etc/kubernetes/pki/ca.crt sang /home/wasadm/vault/k8s-ca.crt
- Cấu hình auth trong Vault (chạy trên 10.100.1.104)
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt="$SA_TOKEN" \
kubernetes_host="https://10.100.1.120:6443" \
kubernetes_ca_cert=@/home/wasadm/vault/k8s-ca.crt
- Tạo secret path và policy chỉ cho phép đọc đúng path
vault kv put demo-db-secrets/demo/db-secret mysql-root-password="Tech@1604" mysql-user="etl_user" mysql-password="123456"
cd /home/wasadm/vault
mkdir demo-ns && cd demo-ns
vim demo-app.hcl
---
path "demo-db-secrets/data/demo/*" {
capabilities = ["read"]
}
# Cho phép đọc metadata KV v2 (ESO thường cần để xử lý versioning)
path "demo-db-secrets/metadata/demo/*" {
capabilities = ["read"]
}
vault policy write demo-app demo-app.hcl
- Tạo role ràng buộc Namespace + ServiceAccount của app
vault write auth/kubernetes/role/demo-app bound_service_account_names="demo-app" bound_service_account_namespaces="demo" policies="demo-app" ttl="300h"
Bây giờ dừng lại 1 chút, để chúng ta xem 1 vài khái niệm trong Vault:
Làm đến đây, sẽ có nhiều bạn thắc mắc : unseal là gì? bật audit để làm gì?
Unseal trong Vault là gì?
- Vault lưu trữ dữ liệu (secret, token, policy...) ở backend storage (VD: Raft, Consul) ở dạng mã hóa.
- Mỗi khi Vault khởi động lại (restart, crash, upgrade…), nó ở trạng thái sealed — tức là dữ liệu vẫn được mã hóa, chưa thể đọc/ghi.
- Để “mở khóa” dữ liệu, bạn phải unseal Vault bằng Unseal Key (có thể chia thành nhiều key, yêu cầu đủ
key-threshold
mới mở được).
Cơ chế bên trong:
- Khi init, Vault tạo một Master Key để giải mã dữ liệu.
- Master Key không được lưu thẳng, mà chia nhỏ (Shamir’s Secret Sharing) thành nhiều Unseal Key.
- Khi bạn chạy
vault operator unseal <key>
, Vault dùng key đó để ghép dần đến đủ threshold, rồi giải mã Master Key trong RAM → Vault sang trạng thái unsealed.
Ví dụ:
vault operator init -key-shares=3 -key-threshold=2
- Sẽ sinh ra 3 Unseal Keys.
- Mỗi lần Vault khởi động, bạn phải nhập đủ 2 trong 3 keys để mở.
Mục tiêu bảo mật: Không ai một mình mở được Vault, tránh trường hợp admin xấu hoặc kẻ tấn công có toàn quyền.
Tiếp theo, chúng ta sẽ xem cách mà deployment lấy giá trị trong Vault Secret như thế nào?
Repo : https://gitlab.com/kiettt164/k8s-lab.git
Thư mục : k8s-svc-lab
App của chúng ta có cấu trúc như sau :
- Code:
import random
import time
import os
import mysql.connector
from datetime import datetime, timedelta
def connect_mysql():
return mysql.connector.connect(
host=os.getenv("MYSQL_HOST"),
user=os.getenv("MYSQL_USER"),
password=os.getenv("MYSQL_PASSWORD"),
database="marketing_db"
)
def generate_random_date(start_year=1970, end_year=2005):
start = datetime(start_year, 1, 1)
end = datetime(end_year, 12, 31)
delta = end - start
random_days = random.randint(0, delta.days)
return (start + timedelta(days=random_days)).date()
def insert_fake_data():
conn = connect_mysql()
cursor = conn.cursor()
for _ in range(100):
full_name = random.choice(["Alice", "Bob", "Charlie", "David", "Batman", "Superman", "Wolverine", "Cyclops", "Spiderman"])
dob = generate_random_date()
phone = f"+84{random.randint(100000000, 999999999)}"
email = f"{full_name.lower()}{random.randint(1,100)}@example.com"
address = random.choice(["Hanoi", "Ho Chi Minh City", "Da Nang", "Hai Phong", "Can Tho", "Gotham", "Star City", "Metropolit"])
balance = round(random.uniform(500.0, 5000.0), 2)
cursor.execute("""
INSERT INTO customers (full_name, dob, phone, email, address, account_balance)
VALUES (%s, %s, %s, %s, %s, %s)
""", (full_name, dob, phone, email, address, balance))
print("Inserted:", full_name, dob, phone, email, address, balance)
conn.commit()
cursor.close()
conn.close()
if __name__ == "__main__":
while True:
insert_fake_data()
print("Done one batch. Sleeping for 30 minutes...\n")
time.sleep(1800)
- Dockerfile:
# Author : Kevin TRan
FROM python:3.10-alpine
WORKDIR /app
COPY generate_fake_data_mysql.py .
RUN pip install kafka-python && pip install --no-cache-dir mysql-connector-python
CMD ["python", "generate_fake_data_mysql.py"]
- Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: generate-fake-data
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: generate-fake-data
template:
metadata:
labels:
app: generate-fake-data
spec:
containers:
- name: generate-fake-data
image: registry.gitlab.com/kiettt164/k8s-lab/app-gen-data:v1
imagePullPolicy: IfNotPresent
resources:
limits:
memory: "128Mi"
cpu: "100m"
requests:
memory: "64Mi"
cpu: "50m"
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
name: db-config
key: host
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: db-secret
key: mysql-user
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: mysql-password
---
apiVersion: v1
kind: Service
metadata:
name: generate-fake-data
namespace: etl-lab-dev
spec:
selector:
app: generate-fake-data
type: ClusterIP
ports:
- name: generate-fake-data
protocol: TCP
port: 5000
targetPort: 5000
Theo cách truyền thống, ta sẽ tạo configmap và secret như sau :
- Configmap:
apiVersion: v1
kind: ConfigMap
metadata:
name: db-config
namespace: demo
data:
host: databases-server
- Secret:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: demo
type: Opaque
data:
mysql-root-password: VGVjaEAxNjA0
mysql-user: ZXRsX3VzZXI=
mysql-password: MTIzNDU2
Nhưng bây giờ, đã có Vault rồi, chúng ta cần map lại secret qua Vault.
Trên master node, tạo thư mục rbac-vaults
mkdir rbac-vaults && cd rbac-vaults
- Tạo SA & RBAC tối thiểu trong namespace
demo
vim demo-app-sa.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: demo-app
namespace: demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-manager
namespace: demo
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get","list","watch","create","update","patch","delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: secret-manager-rb
namespace: demo
subjects:
- kind: ServiceAccount
name: demo-app
namespace: demo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: secret-manager
---
kubectl apply -f demo-app-sa.yaml
- Dùng ExternalSecret để sync lại thành K8s Secret
db-secret
Cài ESO (tự cài CRDs)
kubectl create ns external-secrets
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets -n external-secrets
- Khai báo SecretStore (ESO) trỏ tới Vault
vim secrets-store.yaml
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: vault-demo
namespace: demo
spec:
provider:
vault:
server: "http://10.100.1.104:8200"
path: "demo-db-secrets"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "demo-app"
serviceAccountRef:
name: demo-app
---
kubectl apply -f secrets-store.yaml
#Xac nhan CRDs
kubectl get crd | grep external-secrets
# Bat buoc phai co:
# externalsecrets.external-secrets.io
# secretstores.external-secrets.io
# clustersecretstores.external-secrets.io
# clusterexternalsecrets.external-secrets.io
# pushsecrets.external-secrets.io (tùy bản)
Tạo ExternalSecret để sync về Secret K8s
vim externalsecret-db-secret.yaml
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: db-secret
namespace: demo
spec:
refreshInterval: 1m
secretStoreRef:
name: vault-demo
kind: SecretStore
target:
name: db-secret
creationPolicy: Owner
template:
type: Opaque
data:
- secretKey: mysql-user
remoteRef:
key: "demo/db-secret"
property: "mysql-user"
- secretKey: mysql-password
remoteRef:
key: "demo/db-secret"
property: "mysql-password"
- secretKey: mysql-root-password
remoteRef:
key: "demo/db-secret"
property: "mysql-root-password"
---
kubectl apply -f externalsecret-db-secret.yaml
Bây giờ, deploy app và check xem DB có Data k nhé
kubectl apply -f gen-data-app.yaml
Check logs:
Check data trong mysql server :
HEHE!
Dưới đây là 1 số những video của mình về K8s topic
- Docker to K8s : https://www.youtube.com/watch?v=bPFfetEj1qs&t=1504s
- K8s inbound agent : https://www.youtube.com/watch?v=7Ll_Xx0RM00&t=1s
- MetalLB : https://www.youtube.com/watch?v=KvuI8u3BIZs&t=1s
All rights reserved