Secret - 守護你的機密資料,讓密碼不再裸奔 🔐
還記得上一章我們學會了 ConfigMap,讓應用配置不再寫死在程式碼裡嗎?但是各位,有沒有發現一個問題:ConfigMap 裡的資料都是明文的!如果你把資料庫密碼、API Key、SSL 憑證這些機密資料放在 ConfigMap 裡,那就像是把保險箱密碼貼在保險箱上一樣危險!
就像你是一家銀行的 IT 主管,員工的工作證可以公開展示(ConfigMap),但是金庫密碼、客戶資料加密金鑰這些絕對不能讓所有人都看到。Secret 就是 K8s 專門為了保護這些機密資料而設計的!
✅ 理解 Secret 與 ConfigMap 的差異:學會區分公開配置與機密資料
✅ 掌握 Secret 的四種類型:Generic、TLS、Docker Registry、Service Account
✅ 實作機密資料的安全管理:加密儲存、權限控制、輪替機制
✅ 培養安全意識思維:從「能用就好」到「安全第一」的架構轉變
想像你是一家電商公司的架構師,團隊把所有配置都放在 ConfigMap:
# ❌ 危險的做法:機密資料放在 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: dangerous-config
data:
database_host: "mysql.company.com"
database_port: "3306"
database_user: "admin"
database_password: "SuperSecret123!" # 😱 密碼明文儲存
jwt_secret: "my-super-secret-key" # 😱 JWT 密鑰明文儲存
api_key: "sk-1234567890abcdef" # 😱 API Key 明文儲存
這會造成什麼災難?
🔴 明文儲存風險:任何能訪問 etcd 的人都能看到密碼
# 任何人都能輕易查看
kubectl get configmap dangerous-config -o yaml
# 密碼直接暴露!
🔴 權限控制困難:無法針對機密資料設定特殊權限
# 能看 ConfigMap 的人就能看到所有內容
kubectl get configmap -A # 所有機密資料一覽無遺
🔴 日誌洩漏風險:機密資料可能出現在各種日誌中
# ConfigMap 變更會被記錄在 audit log
kubectl logs kube-apiserver | grep dangerous-config
🔴 版本控制風險:機密資料被提交到 Git 倉庫
# 一旦提交到 Git,就永遠留在歷史記錄中
git log --oneline | grep "add database config"
Secret 就像銀行的保險箱系統:
🏦 銀行保險箱系統 = Secret 機制
├─ 🔐 加密儲存:內容經過 base64 編碼(基礎保護)
├─ 🎫 權限控制:只有授權人員能訪問
├─ 📋 訪問記錄:每次存取都有日誌
├─ 🔄 定期輪替:支援密鑰輪替機制
└─ 🚫 最小權限:按需分配訪問權限
權限控制
實踐,或是搭配 Secret 輪替
機制。我們介紹這個類型就好。想像你是一家五星級飯店的總管,手上管理著各種重要鑰匙:客房鑰匙、保險箱密碼、員工通道卡、VIP 貴賓室密碼。你不可能把這些鑰匙隨便放在櫃檯讓所有人都看得到,而是要分門別類、加密保管,只給有需要的員工相對應的權限。Generic Secret 就是 Kubernetes 世界裡的「總管鑰匙系統」!
Generic Secret 是 Kubernetes 中最靈活、最常用的機密資料儲存方式,就像是一個高級保險箱:
🏨 五星級飯店總管系統 = Generic Secret
├─ 🔐 分類管理:不同類型的機密資料分開儲存
├─ 🎫 權限分級:房務只能拿客房鑰匙,財務只能拿保險箱密碼
├─ 📋 使用記錄:誰在什麼時候取用了哪把鑰匙
├─ 🔄 定期更換:定期更換密碼和鑰匙
└─ 🚫 最小授權:只給必要的權限,不多給一分
TLS Secret - SSL/TLS 憑證管理 🔒
專門用於管理 HTTPS 憑證
Docker Registry Secret - 私有映像庫認證 🐳
用於訪問私有 Docker Registry
Service Account Secret - 服務帳戶認證 👤
用於 Pod 與 Kubernetes API 的認證
你是「賢者金融科技」的 DevOps 工程師,負責部署一套即時交易系統,需要管理:
# 創建命名空間
kubectl create namespace fintech-trading
# 從文件創建
# 先創建機密資料檔案
mkdir -p /tmp/secrets
echo 'SuperSecretDB123!' > /tmp/secrets/db-password
echo 'my-jwt-signing-key-2024' > /tmp/secrets/jwt-secret
echo 'sk-live-abcd1234567890' > /tmp/secrets/payment-api-key
echo 'mail-service-pwd' > /tmp/secrets/smtp-password
# 從檔案創建 Secret
kubectl create secret generic trading-secrets-from-file \
--from-file=/tmp/secrets/ \
--namespace=fintech-trading
# 清理臨時文件(重要!)
rm -rf /tmp/secrets
echo "✅ 從機密資料檔案創建的 Secret 完成,臨時文件已清理!"
echo "✅ Generic Secret 創建完成!"
trading-secrets.yaml
# trading-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: trading-secrets-yaml
namespace: fintech-trading
labels:
app: trading-system
environment: production
security-level: high
type: Opaque # Generic Secret 的類型
data:
# 注意:這裡的值必須是 base64 encode
db-password: U3VwZXJTZWNyZXREQjEyMyE= # SuperSecretDB123!
jwt-secret: bXktand0LXNpZ25pbmcta2V5LTIwMjQ= # my-jwt-signing-key-2024
payment-api-key: c2stbGl2ZS1hYmNkMTIzNDU2Nzg5MA== # sk-live-abcd1234567890
# 應用 YAML 配置
kubectl apply -f trading-secrets.yaml
echo "✅ YAML 方式的 Secret 創建完成!"
trading-app-deployment.yaml
# trading-app-deployment-corrected.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: trading-app
namespace: fintech-trading
spec:
replicas: 3
selector:
matchLabels:
app: trading-app
template:
metadata:
labels:
app: trading-app
spec:
containers:
- name: trading-service
image: nginx:1.21-alpine
ports:
- containerPort: 80
# 使用正確的 Secret 名稱
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: trading-secrets-from-file # ✅ 使用實際存在的 Secret
key: db-password
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: trading-secrets-from-file # ✅ 使用實際存在的 Secret
key: jwt-secret
- name: PAYMENT_API_KEY
valueFrom:
secretKeyRef:
name: trading-secrets-from-file # ✅ 使用實際存在的 Secret
key: payment-api-key
# 文件掛載方式
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
- name: html-volume
mountPath: /usr/share/nginx/html
volumes:
- name: secret-volume
secret:
secretName: trading-secrets-from-file # ✅ 使用實際存在的 Secret
defaultMode: 0400 # 文件權限設定
- name: html-volume
configMap:
name: trading-app-config
---
# 創建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: trading-app-config
namespace: fintech-trading
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>🏦 賢者金融交易系統</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
margin: 0;
padding: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255,255,255,0.1);
padding: 40px;
border-radius: 20px;
backdrop-filter: blur(15px);
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}
.status {
background: rgba(46, 204, 113, 0.2);
padding: 25px;
border-radius: 15px;
margin: 25px 0;
border-left: 5px solid #2ecc71;
}
.secret-info {
background: rgba(52, 152, 219, 0.2);
padding: 20px;
border-radius: 12px;
margin: 15px 0;
border-left: 5px solid #3498db;
}
.pod-info {
background: rgba(155, 89, 182, 0.2);
padding: 15px;
border-radius: 10px;
margin: 10px 0;
border-left: 5px solid #9b59b6;
font-family: 'Courier New', monospace;
}
h1 { font-size: 2.5em; margin-bottom: 10px; }
h2 { color: #2ecc71; }
h3 { color: #3498db; }
.time { font-weight: bold; color: #f39c12; }
</style>
</head>
<body>
<div class="container">
<h1>🏦 賢者金融交易系統</h1>
<p style="font-size: 1.2em; opacity: 0.9;">Kubernetes Secret 管理示範應用</p>
<div class="status">
<h2>✅ 系統狀態:正常運行</h2>
<p>🚀 服務版本:v1.0</p>
<p>🐳 容器映像:nginx:1.21-alpine</p>
<p>📊 當前時間:<span id="time" class="time"></span></p>
<p>🌐 Pod 名稱:<span id="hostname">載入中...</span></p>
</div>
<div class="secret-info">
<h3>🔐 Secret 管理狀態</h3>
<p>✅ Secret 來源:<code>trading-secrets-from-file</code></p>
<p>✅ 環境變數方式:已啟用</p>
<p>✅ 文件掛載方式:已啟用</p>
<p>📁 Secret 掛載路徑:<code>/etc/secrets</code></p>
<p>🔒 文件權限:<code>0400</code> (只讀)</p>
</div>
<div class="secret-info">
<h3>🔑 可用的 Secret Keys</h3>
<ul>
<li><code>db-password</code> - 資料庫密碼</li>
<li><code>jwt-secret</code> - JWT 簽名金鑰</li>
<li><code>payment-api-key</code> - 支付 API 金鑰</li>
</ul>
</div>
<div class="pod-info">
<h3>🎯 測試命令</h3>
<p>查看環境變數:</p>
<code>kubectl exec -n fintech-trading [POD_NAME] -- env | grep -E "(DB_PASSWORD|JWT_SECRET)"</code>
<br><br>
<p>查看 Secret 文件:</p>
<code>kubectl exec -n fintech-trading [POD_NAME] -- ls -la /etc/secrets/</code>
</div>
</div>
<script>
function updateTime() {
document.getElementById('time').textContent = new Date().toLocaleString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function updateHostname() {
document.getElementById('hostname').textContent = 'trading-app-xxx-' + Math.random().toString(36).substr(2, 5);
}
updateTime();
updateHostname();
setInterval(updateTime, 1000);
</script>
</body>
</html>
```
其中
```yaml
volumes:
- name: secret-volume # Volume 的名稱(可自定義)
secret:
secretName: trading-secrets-from-file # 引用的 Secret 名稱
defaultMode: 0400 # 文件權限設定
這段配置就像是在告訴 Kubernetes:
"請幫我準備一個名為 secret-volume 的文件櫃,裡面放的是 trading-secrets-from-file 這個保險箱的內容,而且所有文件的權限都設為 0400"
# Linux 文件權限解讀
0400 = 0 + 4 + 0 + 0
│ │ │ │ │
│ │ │ │ └─ 其他用戶權限:0 (無權限)
│ │ │ └─ 群組權限:0 (無權限)
│ │ └─ 擁有者權限:4 (只讀)
│ └─ 特殊權限:0 (無特殊權限)
└─ 八進位表示法
# 實際效果
-r-------- 1 root root 16 Aug 23 14:24 db-password
# │││││││││
# ││││││││└─ 其他用戶:無權限
# │││││││└─ 其他用戶:無權限
# ││││││└─ 其他用戶:無權限
# │││││└─ 群組:無權限
# ││││└─ 群組:無權限
# │││└─ 群組:無權限
# ││└─ 擁有者:無權限
# │└─ 擁有者:無權限
# └─ 擁有者:可讀取
spec:
serviceAccountName: trading-app-sa # 使用專用的 ServiceAccount
securityContext: # Pod 層級安全設定
runAsNonRoot: true # 不以 root 身份運行
runAsUser: 1000 # 指定用戶 ID
runAsGroup: 1000 # 指定群組 ID
fsGroup: 1000 # 文件系統群組
containers:
- name: trading-service
image: nginx:1.21-alpine
securityContext: # Container 層級安全設定
allowPrivilegeEscalation: false # 禁止權限提升
readOnlyRootFilesystem: true # 根文件系統只讀
capabilities:
drop:
- ALL # 移除所有 capabilities
### 步驟四:驗證 Secret 的使用 🔍
```bash
# 部署應用
> kubectl apply -f trading-app-deployment.yaml
# 等待 Pod 啟動
> kubectl wait --for=condition=ready pod -l app=trading-app -n fintech-trading --timeout=60s
# 檢查 Pod 中的環境變數
> POD_NAME=$(kubectl get pods -n fintech-trading -l app=trading-app -o jsonpath='{.items[0].metadata.name}')
echo "=== 檢查環境變數方式 ==="
> kubectl exec -n fintech-trading $POD_NAME -- env | grep -E "(DB_PASSWORD|JWT_SECRET|PAYMENT_API_KEY)"
DB_PASSWORD=SuperSecretDB123!
JWT_SECRET=my-jwt-signing-key-2024
PAYMENT_API_KEY=sk-live-abcd1234567890
> echo -e "\n=== 檢查文件掛載方式 ==="
> kubectl exec -n fintech-trading $POD_NAME -- ls -la /etc/secrets/
total 8
drwxrwxrwt 3 root root 160 Aug 23 14:24 .
drwxr-xr-x 1 root root 4096 Aug 23 14:24 ..
drwxr-xr-x 2 root root 120 Aug 23 14:24 ..2025_08_23_14_24_50.1599870018
lrwxrwxrwx 1 root root 32 Aug 23 14:24 ..data -> ..2025_08_23_14_24_50.1599870018
lrwxrwxrwx 1 root root 18 Aug 23 14:24 db-password -> ..data/db-password
lrwxrwxrwx 1 root root 17 Aug 23 14:24 jwt-secret -> ..data/jwt-secret
lrwxrwxrwx 1 root root 22 Aug 23 14:24 payment-api-key -> ..data/payment-api-key
lrwxrwxrwx 1 root root 20 Aug 23 14:24 smtp-password -> ..data/smtp-password
> kubectl exec -n fintech-trading $POD_NAME -- cat /etc/secrets/db-password
SuperSecretDB123!
安全
不是一次性的工作,而是持續的過程。就像銀行的保險箱需要定期檢查和維護一樣,你的 Secret 管理系統也需要持續的關注和改進!
因此我們能考慮︰
明天再接續學
🔜 ServiceAccount
🔜 RBAC 權限控制