Merge pull request 'normalize line endings to LF and add envoy dockerfile in deploy/dev' (#2) from feat/envoy-request-limit into main

Reviewed-on: http://103.236.53.208:3000/juwan/juwan-backend/pulls/2
This commit is contained in:
wwweww
2026-04-05 21:27:54 +00:00
39 changed files with 12916 additions and 12246 deletions
+1 -1
View File
@@ -1 +1 @@
deploy/dev/script/*.sh text eol=lf * text=auto eol=lf
+127 -127
View File
@@ -1,128 +1,128 @@
# Logs # Logs
logs logs
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
.pnpm-debug.log* .pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data # Runtime data
pids pids
*.pid *.pid
*.seed *.seed
*.pid.lock *.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover # Directory for instrumented libs generated by jscoverage/JSCover
lib-cov lib-cov
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
coverage coverage
*.lcov *.lcov
# nyc test coverage # nyc test coverage
.nyc_output .nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt .grunt
# Bower dependency directory (https://bower.io/) # Bower dependency directory (https://bower.io/)
bower_components bower_components
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/ jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/) # Snowpack dependency directory (https://snowpack.dev/)
web_modules/ web_modules/
# TypeScript cache # TypeScript cache
*.tsbuildinfo *.tsbuildinfo
# Optional npm cache directory # Optional npm cache directory
.npm .npm
# Optional eslint cache # Optional eslint cache
.eslintcache .eslintcache
# Microbundle cache # Microbundle cache
.rpt2_cache/ .rpt2_cache/
.rts2_cache_cjs/ .rts2_cache_cjs/
.rts2_cache_es/ .rts2_cache_es/
.rts2_cache_umd/ .rts2_cache_umd/
# Optional REPL history # Optional REPL history
.node_repl_history .node_repl_history
# Output of 'npm pack' # Output of 'npm pack'
*.tgz *.tgz
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
# dotenv environment variables file # dotenv environment variables file
dev test dev test
.env.test .env.test
.env.production .env.production
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache
.parcel-cache .parcel-cache
# Next.js build output # Next.js build output
.next .next
out out
# Nuxt.js build / generate output # Nuxt.js build / generate output
.nuxt .nuxt
dist dist
# Gatsby files # Gatsby files
.cache/ .cache/
# Comment in the public line in if your project uses Gatsby and not Next.js # Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support # https://nextjs.org/blog/next-9-1#public-directory-support
# public # public
# vuepress build output # vuepress build output
.vuepress/dist .vuepress/dist
# Serverless directories # Serverless directories
.serverless/ .serverless/
# FuseBox cache # FuseBox cache
.fusebox/ .fusebox/
# DynamoDB Local files # DynamoDB Local files
.dynamodb/ .dynamodb/
# TernJS port file # TernJS port file
.tern-port .tern-port
# Stores VSCode versions used for testing VSCode extensions # Stores VSCode versions used for testing VSCode extensions
.vscode-test .vscode-test
# yarn v2 # yarn v2
.yarn/cache .yarn/cache
.yarn/unplugged .yarn/unplugged
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
# End of https://mrkandreev.name/snippets/gitignore-generator/#Node # End of https://mrkandreev.name/snippets/gitignore-generator/#Node
DockerFile DockerFile
.idea .idea
# Go compiled binaries # Go compiled binaries
app/*/api/api app/*/api/api
app/*/rpc/rpc app/*/rpc/rpc
app/*/mq/mq app/*/mq/mq
+8 -8
View File
@@ -1,8 +1,8 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
# Editor-based HTTP Client requests # Editor-based HTTP Client requests
/httpRequests/ /httpRequests/
# Datasource local storage ignored files # Datasource local storage ignored files
/dataSources/ /dataSources/
/dataSources.local.xml /dataSources.local.xml
+7 -7
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/st-1-example.iml" filepath="$PROJECT_DIR$/.idea/st-1-example.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/st-1-example.iml" filepath="$PROJECT_DIR$/.idea/st-1-example.iml" />
</modules> </modules>
</component> </component>
</project> </project>
+8 -8
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" /> <component name="Go" enabled="true" />
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>
Generated
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>
+200 -200
View File
@@ -1,200 +1,200 @@
import { search } from "@inquirer/prompts"; import { search } from "@inquirer/prompts";
import { execa } from "execa"; import { execa } from "execa";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { glob } from "glob"; import { glob } from "glob";
import { task } from "hereby"; import { task } from "hereby";
import path from "node:path"; import path from "node:path";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util"; import { parseArgs } from "node:util";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { values } = parseArgs({ const { values } = parseArgs({
args: process.argv.slice(3), args: process.argv.slice(3),
options: { options: {
server: { type: "string", short: "s", multiple: true }, // 服务名称 server: { type: "string", short: "s", multiple: true }, // 服务名称
type: { type: "string", short: "t" }, // 生成类型:api, rpc, docker type: { type: "string", short: "t" }, // 生成类型:api, rpc, docker
imageName: { type: "string", short: "i" }, // 镜像名字 imageName: { type: "string", short: "i" }, // 镜像名字
} }
}) })
const Paths = { const Paths = {
root: __dirname, root: __dirname,
desc: path.join(__dirname, "desc"), desc: path.join(__dirname, "desc"),
app: path.join(__dirname, "app"), app: path.join(__dirname, "app"),
getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)), getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)),
getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName), getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName),
pathlistToChoices: (filePaths) => filePaths.map(filePath => ({ pathlistToChoices: (filePaths) => filePaths.map(filePath => ({
title: Paths.getServiceName(filePath), title: Paths.getServiceName(filePath),
value: filePath, value: filePath,
})), })),
getDescFiles: async (pattern) => { getDescFiles: async (pattern) => {
return await glob(pattern, { cwd: Paths.desc, absolute: true }); return await glob(pattern, { cwd: Paths.desc, absolute: true });
}, },
getAllApi: async () => { getAllApi: async () => {
const apiPattern = "api/*.api"; const apiPattern = "api/*.api";
return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern)); return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern));
}, },
getAllProto: async () => { getAllProto: async () => {
const protoPattern = "rpc/*.proto"; const protoPattern = "rpc/*.proto";
return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern)); return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern));
}, },
getAllservice: async () => { getAllservice: async () => {
let all = []; let all = [];
const services = await fs.readdir(Paths.app); const services = await fs.readdir(Paths.app);
for (const service of services) { for (const service of services) {
const servicePath = path.join(Paths.app, service); const servicePath = path.join(Paths.app, service);
const svcTypes = await fs.readdir(servicePath); const svcTypes = await fs.readdir(servicePath);
svcTypes.map(svcType => all.push({ svcTypes.map(svcType => all.push({
title: `${service} - ${svcType}`, title: `${service} - ${svcType}`,
value: path.join(servicePath, svcType, svcType !== "rpc" ? `${service}.go` : "pb.go"), value: path.join(servicePath, svcType, svcType !== "rpc" ? `${service}.go` : "pb.go"),
})); }));
} }
return all; return all;
}, },
} }
const Generators = { const Generators = {
async api(apiFile) { async api(apiFile) {
const serviceName = Paths.getServiceName(apiFile); const serviceName = Paths.getServiceName(apiFile);
const outputDir = path.join(Paths.getOutputDir(serviceName), 'api'); const outputDir = path.join(Paths.getOutputDir(serviceName), 'api');
await fs.mkdir(outputDir, { recursive: true }); await fs.mkdir(outputDir, { recursive: true });
await run('goctl', [ await run('goctl', [
'api', 'go', 'api', 'go',
'--api', apiFile, '--api', apiFile,
'--dir', outputDir, '--dir', outputDir,
'--style', 'goZero' '--style', 'goZero'
]); ]);
}, },
async rpc(protoFile) { async rpc(protoFile) {
const serviceName = Paths.getServiceName(protoFile); const serviceName = Paths.getServiceName(protoFile);
const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc'); const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc');
await fs.mkdir(outputDir, { recursive: true }); await fs.mkdir(outputDir, { recursive: true });
await run('goctl', [ await run('goctl', [
'rpc', 'protoc', protoFile, 'rpc', 'protoc', protoFile,
`--proto_path=${path.join(Paths.desc, "rpc",)}`, `--proto_path=${path.join(Paths.desc, "rpc",)}`,
`--go_out=${outputDir}`, `--go_out=${outputDir}`,
`--go-grpc_out=${outputDir}`, `--go-grpc_out=${outputDir}`,
`--zrpc_out=${outputDir}`, `--zrpc_out=${outputDir}`,
'--style=goZero', '--style=goZero',
]); ]);
}, },
async docker(servicePath) { async docker(servicePath) {
const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true }); const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true });
if (dockerFiles.length !== 0) { if (dockerFiles.length !== 0) {
fs.rm(dockerFiles[0], { force: true }); fs.rm(dockerFiles[0], { force: true });
} }
await run('goctl', [ await run('goctl', [
"docker", "--go", path.relative(__dirname, servicePath) "docker", "--go", path.relative(__dirname, servicePath)
]) ])
} }
}; };
const GenerateConfig = { const GenerateConfig = {
api: { api: {
getChoices: () => Paths.getAllApi(), getChoices: () => Paths.getAllApi(),
prompt: "Select an API description file", prompt: "Select an API description file",
generate: (path) => Generators.api(path), generate: (path) => Generators.api(path),
}, },
rpc: { rpc: {
getChoices: () => Paths.getAllProto(), getChoices: () => Paths.getAllProto(),
prompt: "Select a proto file", prompt: "Select a proto file",
generate: (path) => Generators.rpc(path), generate: (path) => Generators.rpc(path),
}, },
docker: { docker: {
getChoices: () => Paths.getAllservice(), getChoices: () => Paths.getAllservice(),
prompt: "Select a service to generate Dockerfile", prompt: "Select a service to generate Dockerfile",
generate: (path) => Generators.docker(path), generate: (path) => Generators.docker(path),
} }
}; };
async function run(cmd, args, opts = {}) { async function run(cmd, args, opts = {}) {
console.log(`>> ${cmd} ${args.join(' ')}`); console.log(`>> ${cmd} ${args.join(' ')}`);
return execa(cmd, args, { return execa(cmd, args, {
stdio: 'inherit', stdio: 'inherit',
...opts ...opts
}); });
} }
async function searchSelector(chooses, message) { async function searchSelector(chooses, message) {
const fuse = new Fuse(chooses, { const fuse = new Fuse(chooses, {
keys: ['title'], keys: ['title'],
threshold: 0.4, threshold: 0.4,
}) })
return search({ return search({
message, message,
source: async (term) => { source: async (term) => {
if (!term) { if (!term) {
return chooses.map(s => ({ name: s.title, value: s.value })); return chooses.map(s => ({ name: s.title, value: s.value }));
} }
const result = fuse.search(term); const result = fuse.search(term);
return result.map(s => ({ name: s.item.title, value: s.item.value })); return result.map(s => ({ name: s.item.title, value: s.item.value }));
} }
}) })
} }
async function generateHandle() { async function generateHandle() {
const type = values.type; const type = values.type;
if (!type || !GenerateConfig[type]) { if (!type || !GenerateConfig[type]) {
console.error("Please specify valid -t <api|rpc|docker>"); console.error("Please specify valid -t <api|rpc|docker>");
return; return;
} }
const config = GenerateConfig[type]; const config = GenerateConfig[type];
const input = values.server const input = values.server
? (Array.isArray(values.server) ? values.server[0] : values.server) ? (Array.isArray(values.server) ? values.server[0] : values.server)
: await searchSelector(await config.getChoices(), config.prompt); : await searchSelector(await config.getChoices(), config.prompt);
await config.generate(input); await config.generate(input);
} }
async function buildImage(imageName) { async function buildImage(imageName) {
await run("docker", ["build", "-t", imageName, "."]) await run("docker", ["build", "-t", imageName, "."])
} }
export const init = task({ export const init = task({
name: "init", name: "init",
desc: "initialize the project", desc: "initialize the project",
run: async () => { run: async () => {
await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]); await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]);
} }
}) })
export const tidy = task({ export const tidy = task({
name: "tidy", name: "tidy",
desc: "tidy go.mod and go.sum", desc: "tidy go.mod and go.sum",
run: async () => { run("go", ["mod", "tidy"]) }, run: async () => { run("go", ["mod", "tidy"]) },
}) })
export const build = task({ export const build = task({
name: "build", name: "build",
desc: "build docker image", desc: "build docker image",
run: async () => { run: async () => {
if (values.imageName) { if (values.imageName) {
buildImage(values.imageName); buildImage(values.imageName);
} else { } else {
console.error("Please specify image name with -i <imageName>"); console.error("Please specify image name with -i <imageName>");
} }
} }
}) })
export const gen = task({ export const gen = task({
name: "gen", name: "gen",
desc: "generate API/RPC service code", desc: "generate API/RPC service code",
run: generateHandle, run: generateHandle,
}); });
+233 -233
View File
@@ -1,233 +1,233 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
labels: labels:
app: user-rpc app: user-rpc
spec: spec:
replicas: 3 replicas: 3
revisionHistoryLimit: 5 revisionHistoryLimit: 5
selector: selector:
matchLabels: matchLabels:
app: user-rpc app: user-rpc
template: template:
metadata: metadata:
labels: labels:
app: user-rpc app: user-rpc
spec: spec:
serviceAccountName: find-endpoints serviceAccountName: find-endpoints
initContainers: # 等待数据库就绪的 Init Container 不影响资源使用但是影响调度策略(也可以忽略不计) initContainers: # 等待数据库就绪的 Init Container 不影响资源使用但是影响调度策略(也可以忽略不计)
- name: wait-for-db - name: wait-for-db
image: busybox:1.36 image: busybox:1.36
command: command:
[ [
"sh", "sh",
"-c", "-c",
'until nc -z -v -w5 user-db-rw 5432; do echo "Waiting for database..."; sleep 2; done;', 'until nc -z -v -w5 user-db-rw 5432; do echo "Waiting for database..."; sleep 2; done;',
] ]
containers: containers:
- name: user-rpc - name: user-rpc
image: user-rpc:v1 image: user-rpc:v1
ports: ports:
- containerPort: 9001 - containerPort: 9001
- containerPort: 4001 - containerPort: 4001
env: env:
- name: DB_URI - name: DB_URI
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: user-db-app name: user-db-app
key: uri key: uri
- name: REDIS_HOST - name: REDIS_HOST
value: "user-redis-sentinel-sentinel.juwan:26379" value: "user-redis-sentinel-sentinel.juwan:26379"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: user-redis name: user-redis
key: password key: password
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: 9001 port: 9001
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: 9001 port: 9001
initialDelaySeconds: 15 initialDelaySeconds: 15
periodSeconds: 20 periodSeconds: 20
resources: resources:
requests: requests:
cpu: 500m cpu: 500m
memory: 512Mi memory: 512Mi
limits: limits:
cpu: 1000m cpu: 1000m
memory: 1024Mi memory: 1024Mi
volumeMounts: volumeMounts:
- name: timezone - name: timezone
mountPath: /etc/localtime mountPath: /etc/localtime
volumes: volumes:
- name: timezone - name: timezone
hostPath: hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai path: /usr/share/zoneinfo/Asia/Shanghai
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: user-rpc-svc name: user-rpc-svc
namespace: juwan namespace: juwan
annotations: annotations:
prometheus.io/scrape: "true" prometheus.io/scrape: "true"
prometheus.io/port: "4001" prometheus.io/port: "4001"
prometheus.io/path: "/metrics" prometheus.io/path: "/metrics"
spec: spec:
ports: ports:
- name: rpc - name: rpc
port: 9001 port: 9001
targetPort: 9001 targetPort: 9001
- name: metrics - name: metrics
port: 4001 port: 4001
targetPort: 4001 targetPort: 4001
selector: selector:
app: user-rpc app: user-rpc
--- ---
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
name: user-rpc-hpa-c name: user-rpc-hpa-c
namespace: juwan namespace: juwan
labels: labels:
app: user-rpc-hpa-c app: user-rpc-hpa-c
spec: spec:
scaleTargetRef: scaleTargetRef:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
name: user-rpc name: user-rpc
minReplicas: 3 minReplicas: 3
maxReplicas: 10 maxReplicas: 10
metrics: metrics:
- type: Resource - type: Resource
resource: resource:
name: cpu name: cpu
target: target:
type: Utilization type: Utilization
averageUtilization: 80 averageUtilization: 80
--- ---
apiVersion: autoscaling/v2 apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
name: user-rpc-hpa-m name: user-rpc-hpa-m
namespace: juwan namespace: juwan
labels: labels:
app: user-rpc-hpa-m app: user-rpc-hpa-m
spec: spec:
scaleTargetRef: scaleTargetRef:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
name: user-rpc name: user-rpc
minReplicas: 3 minReplicas: 3
maxReplicas: 10 maxReplicas: 10
metrics: metrics:
- type: Resource - type: Resource
resource: resource:
name: memory name: memory
target: target:
type: Utilization type: Utilization
averageUtilization: 80 averageUtilization: 80
--- ---
# Redis 主从复制 # Redis 主从复制
apiVersion: redis.redis.opstreelabs.in/v1beta2 apiVersion: redis.redis.opstreelabs.in/v1beta2
kind: RedisReplication kind: RedisReplication
metadata: metadata:
name: user-redis name: user-redis
namespace: juwan namespace: juwan
spec: spec:
clusterSize: 3 clusterSize: 3
kubernetesConfig: kubernetesConfig:
image: quay.io/opstree/redis:v7.0.12 image: quay.io/opstree/redis:v7.0.12
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
resources: resources:
requests: requests:
cpu: 100m cpu: 100m
memory: 128Mi memory: 128Mi
limits: limits:
cpu: 500m cpu: 500m
memory: 512Mi memory: 512Mi
redisSecret: redisSecret:
name: user-redis name: user-redis
key: password key: password
redisExporter: redisExporter:
enabled: true enabled: true
image: quay.io/opstree/redis-exporter:latest image: quay.io/opstree/redis-exporter:latest
imagePullPolicy: Always imagePullPolicy: Always
podSecurityContext: podSecurityContext:
runAsUser: 1000 runAsUser: 1000
fsGroup: 1000 fsGroup: 1000
storage: storage:
volumeClaimTemplate: volumeClaimTemplate:
spec: spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
requests: requests:
storage: 1Gi storage: 1Gi
--- ---
# Sentinel 监控 # Sentinel 监控
apiVersion: redis.redis.opstreelabs.in/v1beta2 apiVersion: redis.redis.opstreelabs.in/v1beta2
kind: RedisSentinel kind: RedisSentinel
metadata: metadata:
name: user-redis-sentinel name: user-redis-sentinel
namespace: juwan namespace: juwan
spec: spec:
clusterSize: 3 clusterSize: 3
kubernetesConfig: kubernetesConfig:
image: quay.io/opstree/redis-sentinel:v7.0.12 image: quay.io/opstree/redis-sentinel:v7.0.12
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
resources: resources:
requests: requests:
cpu: 100m cpu: 100m
memory: 128Mi memory: 128Mi
limits: limits:
cpu: 500m cpu: 500m
memory: 512Mi memory: 512Mi
podSecurityContext: podSecurityContext:
runAsUser: 1000 runAsUser: 1000
fsGroup: 1000 fsGroup: 1000
redisSentinelConfig: redisSentinelConfig:
redisReplicationName: user-redis redisReplicationName: user-redis
masterGroupName: mymaster masterGroupName: mymaster
redisPort: "6379" redisPort: "6379"
quorum: "2" quorum: "2"
downAfterMilliseconds: "5000" downAfterMilliseconds: "5000"
failoverTimeout: "10000" failoverTimeout: "10000"
parallelSyncs: "1" parallelSyncs: "1"
--- ---
# PostgreSQL 集群 # PostgreSQL 集群
apiVersion: postgresql.cnpg.io/v1 apiVersion: postgresql.cnpg.io/v1
kind: Cluster kind: Cluster
metadata: metadata:
namespace: juwan namespace: juwan
name: user-db name: user-db
spec: spec:
instances: 3 instances: 3
backup: backup:
barmanObjectStore: barmanObjectStore:
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/ destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
endpointURL: https://cn-nb1.rains3.com endpointURL: https://cn-nb1.rains3.com
s3Credentials: s3Credentials:
accessKeyId: accessKeyId:
name: rc-creds name: rc-creds
key: SOucqRaJr4OyfcIu key: SOucqRaJr4OyfcIu
secretAccessKey: secretAccessKey:
name: rc-creds name: rc-creds
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
wal: wal:
compression: gzip compression: gzip
storage: storage:
size: 1Gi size: 1Gi
monitoring: monitoring:
enablePodMonitor: true enablePodMonitor: true
+260 -260
View File
@@ -1,260 +1,260 @@
# Converter - 通用结构体转换工具 # Converter - 通用结构体转换工具
利用 Go 反射机制,实现自动的 model 到 protobuf 结构体转换。 利用 Go 反射机制,实现自动的 model 到 protobuf 结构体转换。
## 功能特性 ## 功能特性
**自动字段映射** - 自动匹配同名字段并赋值 **自动字段映射** - 自动匹配同名字段并赋值
**智能类型转换** - 自动处理常见类型转换 **智能类型转换** - 自动处理常见类型转换
**通用设计** - 支持任何 model 和 pb 结构体,无需手动编写 **通用设计** - 支持任何 model 和 pb 结构体,无需手动编写
**灵活扩展** - 支持自定义类型转换规则 **灵活扩展** - 支持自定义类型转换规则
## 支持的类型转换 ## 支持的类型转换
| 源类型 | 目标类型 | 说明 | | 源类型 | 目标类型 | 说明 |
|-------|---------|------| |-------|---------|------|
| `time.Time` | `int64` | 转换为 Unix 时间戳 | | `time.Time` | `int64` | 转换为 Unix 时间戳 |
| `sql.NullTime` | `int64` | 有效时自动转换,无效则为 0 | | `sql.NullTime` | `int64` | 有效时自动转换,无效则为 0 |
| `sql.NullTime` | `time.Time` | 有效时自动转换,无效则为零值 | | `sql.NullTime` | `time.Time` | 有效时自动转换,无效则为零值 |
| `sql.NullInt64` | `int64` | 有效时自动转换,无效则为 0 | | `sql.NullInt64` | `int64` | 有效时自动转换,无效则为 0 |
| `sql.NullString` | `string` | 有效时自动转换,无效则为空字符串 | | `sql.NullString` | `string` | 有效时自动转换,无效则为空字符串 |
| `sql.NullBool` | `bool` | 有效时自动转换,无效则为 false | | `sql.NullBool` | `bool` | 有效时自动转换,无效则为 false |
| `int` | `int64` | 自动转换 | | `int` | `int64` | 自动转换 |
| `int64` | `int` | 自动转换 | | `int64` | `int` | 自动转换 |
| 相同类型 | 相同类型 | 直接复制 | | 相同类型 | 相同类型 | 直接复制 |
## 核心函数 ## 核心函数
### 1. StructToStruct - 单个结构体转换 ### 1. StructToStruct - 单个结构体转换
```go ```go
func StructToStruct(src, dst interface{}) error func StructToStruct(src, dst interface{}) error
``` ```
**参数:** **参数:**
- `src` - 源结构体(可以是指针或值类型) - `src` - 源结构体(可以是指针或值类型)
- `dst` - 目标结构体(必须是指针) - `dst` - 目标结构体(必须是指针)
**示例:** **示例:**
```go ```go
import "app/common/converter" import "app/common/converter"
// 单个 models 转 pb // 单个 models 转 pb
user, _ := m.FindOne(ctx, userId) user, _ := m.FindOne(ctx, userId)
pbUser := &pb.Users{} pbUser := &pb.Users{}
converter.StructToStruct(user, pbUser) converter.StructToStruct(user, pbUser)
// 或直接点对点转换 // 或直接点对点转换
pbUser := &pb.Users{} pbUser := &pb.Users{}
_ = converter.StructToStruct(user, pbUser) _ = converter.StructToStruct(user, pbUser)
``` ```
### 2. SliceToSlice - 切片转换 ### 2. SliceToSlice - 切片转换
```go ```go
func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error) func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error)
``` ```
**参数:** **参数:**
- `src` - 源切片 - `src` - 源切片
- `dstSliceType` - 目标切片类型(用于推导元素类型) - `dstSliceType` - 目标切片类型(用于推导元素类型)
**示例:** **示例:**
```go ```go
// 多个 models 转 pb // 多个 models 转 pb
users := []*models.Users{user1, user2, user3} users := []*models.Users{user1, user2, user3}
pbUsersIface, _ := converter.SliceToSlice(users, []*pb.Users{}) pbUsersIface, _ := converter.SliceToSlice(users, []*pb.Users{})
pbUsers := pbUsersIface.([]*pb.Users) pbUsers := pbUsersIface.([]*pb.Users)
``` ```
### 3. UserModelToPb - Users 专用转换(推荐) ### 3. UserModelToPb - Users 专用转换(推荐)
```go ```go
func UserModelToPb(user *models.Users) *pb.Users func UserModelToPb(user *models.Users) *pb.Users
``` ```
简化的 Users model 转 pb 的快捷函数。 简化的 Users model 转 pb 的快捷函数。
**示例:** **示例:**
```go ```go
user, _ := m.FindOne(ctx, userId) user, _ := m.FindOne(ctx, userId)
pbUser := converter.UserModelToPb(user) pbUser := converter.UserModelToPb(user)
``` ```
### 4. UserModelsToPb - Users 批量转换(推荐) ### 4. UserModelsToPb - Users 批量转换(推荐)
```go ```go
func UserModelsToPb(users []*models.Users) []*pb.Users func UserModelsToPb(users []*models.Users) []*pb.Users
``` ```
简化的批量转换快捷函数。 简化的批量转换快捷函数。
**示例:** **示例:**
```go ```go
users, _ := m.FindAll(ctx) users, _ := m.FindAll(ctx)
pbUsers := converter.UserModelsToPb(users) pbUsers := converter.UserModelsToPb(users)
``` ```
## 使用场景 ## 使用场景
### 场景 1:在 Logic 层直接转换 ### 场景 1:在 Logic 层直接转换
```go ```go
package logic package logic
import ( import (
"context" "context"
"app/common/converter" "app/common/converter"
"app/users/rpc/internal/models" "app/users/rpc/internal/models"
"app/users/rpc/pb" "app/users/rpc/pb"
) )
type GetUserByIdLogic struct { type GetUserByIdLogic struct {
ctx context.Context ctx context.Context
svcCtx *svc.ServiceContext svcCtx *svc.ServiceContext
} }
func (l *GetUserByIdLogic) GetUserById(req *pb.GetUserByIdReq) (*pb.Users, error) { func (l *GetUserByIdLogic) GetUserById(req *pb.GetUserByIdReq) (*pb.Users, error) {
// 查询数据库 // 查询数据库
user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.UserId) user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.UserId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 直接转换,无需手动赋值每个字段 // 直接转换,无需手动赋值每个字段
pbUser := converter.UserModelToPb(user) pbUser := converter.UserModelToPb(user)
return pbUser, nil return pbUser, nil
} }
``` ```
### 场景 2:批量操作 ### 场景 2:批量操作
```go ```go
func (l *ListUsersLogic) ListUsers(req *pb.ListUsersReq) (*pb.ListUsersResp, error) { func (l *ListUsersLogic) ListUsers(req *pb.ListUsersReq) (*pb.ListUsersResp, error) {
users, err := l.svcCtx.UsersModel.FindAll(l.ctx) users, err := l.svcCtx.UsersModel.FindAll(l.ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 批量转换 // 批量转换
pbUsers := converter.UserModelsToPb(users) pbUsers := converter.UserModelsToPb(users)
return &pb.ListUsersResp{ return &pb.ListUsersResp{
Users: pbUsers, Users: pbUsers,
}, nil }, nil
} }
``` ```
### 场景 3:搜索/过滤结果 ### 场景 3:搜索/过滤结果
```go ```go
func (l *SearchUsersLogic) SearchUsers(req *pb.SearchReq) (*pb.SearchResp, error) { func (l *SearchUsersLogic) SearchUsers(req *pb.SearchReq) (*pb.SearchResp, error) {
// 搜索数据库 // 搜索数据库
results, err := l.svcCtx.UsersModel.SearchByKeyword(l.ctx, req.Keyword) results, err := l.svcCtx.UsersModel.SearchByKeyword(l.ctx, req.Keyword)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pbUsers := converter.UserModelsToPb(results) pbUsers := converter.UserModelsToPb(results)
return &pb.SearchResp{ return &pb.SearchResp{
Results: pbUsers, Results: pbUsers,
}, nil }, nil
} }
``` ```
## 处理特殊字段 ## 处理特殊字段
### NULLable 字段 ### NULLable 字段
当源字段是 `sql.NullTime` 或其他 `sql.Null*` 类型时,转换器会自动处理: 当源字段是 `sql.NullTime` 或其他 `sql.Null*` 类型时,转换器会自动处理:
```go ```go
// sql.NullTime -> int64(有效情况) // sql.NullTime -> int64(有效情况)
user.DeletedAt = sql.NullTime{ user.DeletedAt = sql.NullTime{
Time: time.Now(), Time: time.Now(),
Valid: true, Valid: true,
} }
// 转换后 pb.Users.DeletedAt 会包含 Unix 时间戳 // 转换后 pb.Users.DeletedAt 会包含 Unix 时间戳
// sql.NullTime -> int64(无效情况) // sql.NullTime -> int64(无效情况)
user.DeletedAt = sql.NullTime{ user.DeletedAt = sql.NullTime{
Valid: false, Valid: false,
} }
// 转换后 pb.Users.DeletedAt 为 0 // 转换后 pb.Users.DeletedAt 为 0
``` ```
### 时间戳字段 ### 时间戳字段
数据库中的 `time.Time` 字段会自动转换为 protobuf 中的 `int64` Unix 时间戳: 数据库中的 `time.Time` 字段会自动转换为 protobuf 中的 `int64` Unix 时间戳:
```go ```go
// Model // Model
type Users struct { type Users struct {
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
DeletedAt sql.NullTime `db:"deleted_at"` DeletedAt sql.NullTime `db:"deleted_at"`
} }
// Protobuf // Protobuf
type Users struct { type Users struct {
CreatedAt int64 // 自动转换为 Unix 时间戳 CreatedAt int64 // 自动转换为 Unix 时间戳
UpdatedAt int64 // 自动转换为 Unix 时间戳 UpdatedAt int64 // 自动转换为 Unix 时间戳
DeletedAt int64 // 有效时转换,无效时为 0 DeletedAt int64 // 有效时转换,无效时为 0
} }
``` ```
## 扩展 - 添加自定义类型转换 ## 扩展 - 添加自定义类型转换
如果需要支持新的类型转换,可以在 `generic.go``assignValue` 函数中添加: 如果需要支持新的类型转换,可以在 `generic.go``assignValue` 函数中添加:
```go ```go
// 处理自定义类型 MyType -> int32 的转换 // 处理自定义类型 MyType -> int32 的转换
if srcType == reflect.TypeOf(MyType{}) && dstType.Kind() == reflect.Int32 { if srcType == reflect.TypeOf(MyType{}) && dstType.Kind() == reflect.Int32 {
mt := srcField.Interface().(MyType) mt := srcField.Interface().(MyType)
dstField.SetInt(int64(mt.SomeIntField)) dstField.SetInt(int64(mt.SomeIntField))
return nil return nil
} }
``` ```
## 性能考虑 ## 性能考虑
- 反射操作相对于直接赋值会有性能开销(通常很小) - 反射操作相对于直接赋值会有性能开销(通常很小)
- 如果需要转换大量数据(>10000 条),考虑性能测试 - 如果需要转换大量数据(>10000 条),考虑性能测试
- 对于热点代码路径,可以写针对性的转换函数 - 对于热点代码路径,可以写针对性的转换函数
## 错误处理 ## 错误处理
```go ```go
err := converter.StructToStruct(src, dst) err := converter.StructToStruct(src, dst)
if err != nil { if err != nil {
log.Printf("转换失败: %v", err) log.Printf("转换失败: %v", err)
// 处理错误 // 处理错误
} }
``` ```
大多数字段级别的转换错误会被忽略(自动跳过),但结构化错误(如 dst 不是指针)会返回。 大多数字段级别的转换错误会被忽略(自动跳过),但结构化错误(如 dst 不是指针)会返回。
## 常见问题 ## 常见问题
**Q: 字段名必须完全相同吗?** **Q: 字段名必须完全相同吗?**
A: 是的,转换器通过反射按字段名匹配。如果 model 字段名是 `UserId`pb 字段也必须是 `UserId` A: 是的,转换器通过反射按字段名匹配。如果 model 字段名是 `UserId`pb 字段也必须是 `UserId`
**Q: 如果某个字段转换失败怎么办?** **Q: 如果某个字段转换失败怎么办?**
A: 单个字段的转换失败会被忽略,继续处理其他字段。确保其他字段正确设置。 A: 单个字段的转换失败会被忽略,继续处理其他字段。确保其他字段正确设置。
**Q: 能否自定义字段映射规则(比如 `db_name` -> `pbName`)?** **Q: 能否自定义字段映射规则(比如 `db_name` -> `pbName`)?**
A: 当前不支持。如果需要,应该在 protobuf 定义中使用与 model 相同的字段名。 A: 当前不支持。如果需要,应该在 protobuf 定义中使用与 model 相同的字段名。
**Q: 转换速度快吗?** **Q: 转换速度快吗?**
A: 反射会有性能开销,但对于大多数应用场景是可接受的。如果有极端性能要求,可以手写转换函数。 A: 反射会有性能开销,但对于大多数应用场景是可接受的。如果有极端性能要求,可以手写转换函数。
## 相关文件 ## 相关文件
- `generic.go` - 通用转换函数核心实现 - `generic.go` - 通用转换函数核心实现
- `user_converter.go` - Users model 专用转换函数(示例) - `user_converter.go` - Users model 专用转换函数(示例)
+30 -30
View File
@@ -1,30 +1,30 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
T2Eqz8rRnaIe T2Eqz8rRnaIe
-----END CERTIFICATE----- -----END CERTIFICATE-----
+52 -52
View File
@@ -1,52 +1,52 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
Izss0+mp4ijKEFQuCGCkxjFmxUEq Izss0+mp4ijKEFQuCGCkxjFmxUEq
-----END PRIVATE KEY----- -----END PRIVATE KEY-----
+38 -27
View File
@@ -35,6 +35,15 @@ services:
timeout: 3s timeout: 3s
retries: 10 retries: 10
rl-redis:
image: redis:${REDIS_VERSION:-8}
container_name: ${REDIS_CONTAINER_NAME:-rl-redis-dev-server}
profiles:
- infra
ports:
- "6380:6379"
restart: unless-stopped
kafka: kafka:
image: apache/kafka:4.0.1 image: apache/kafka:4.0.1
container_name: juwan-kafka container_name: juwan-kafka
@@ -77,40 +86,42 @@ services:
condition: service_started condition: service_started
envoy-gateway: envoy-gateway:
image: envoyproxy/envoy:v1.31-latest build:
container_name: juwan-envoy-gateway context: ../deploy/dev/envoy
restart: unless-stopped image: envoy-gateway:latest
command: container_name: ${ENVOY_GATEWAY_CONTAINER_NAME:-envoy-gateway-dev-server}
- /usr/local/bin/envoy
- -c
- /etc/envoy/envoy.yaml
- --log-level
- info
ports: ports:
- "18080:8080" - "8080:8080"
volumes: - "9901:9901"
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
depends_on: depends_on:
authz-adapter: authz-adapter:
condition: service_started condition: service_started
users-api: required: false
condition: service_started user-api:
player-api:
condition: service_started
game-api:
condition: service_started
shop-api:
condition: service_started
order-api:
condition: service_started
wallet-api:
condition: service_started
community-api:
condition: service_started
objectstory-api:
condition: service_started condition: service_started
required: false
email-api: email-api:
condition: service_started condition: service_started
required: false
restart: unless-stopped
ratelimit:
image: ratelimit:latest
container_name: rl-service
restart: unless-stopped
environment:
- REDIS_SOCKET_TYPE=tcp
- REDIS_URL=rl-redis:6379
- USE_STATSD=false
- RUNTIME_ROOT=/data
- RUNTIME_SUBDIRECTORY=ratelimit
- RUNTIME_WATCH_ROOT=true # 热重载
- LOG_LEVEL=debug
volumes:
- ./rls/ratelimit.yaml:/data/ratelimit/config/ratelimit.yaml:ro
ports:
- "8081:8081"
- "6070:6070"
# ==================== RPC 层 ==================== # ==================== RPC 层 ====================
user-rpc: user-rpc:
+626
View File
@@ -0,0 +1,626 @@
static_resources:
listeners:
- name: ingress_http
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
generate_request_id: true
use_remote_address: true
xff_num_trusted_hops: 1
route_config:
name: local_route
virtual_hosts:
- name: juwan_services
domains: [ "*" ]
routes:
- match:
path: /healthz
direct_response:
status: 200
body:
inline_string: ok
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/auth/login
route:
cluster: user_api_cluster
timeout: 30s
rate_limits:
- actions:
- generic_key:
descriptor_value: login
- remote_address: {}
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/auth/register
route:
cluster: user_api_cluster
timeout: 30s
rate_limits:
- actions:
- generic_key:
descriptor_value: register
- generic_key:
descriptor_key: "period"
descriptor_value: "minute"
- remote_address: {}
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/auth/forgot-password
route:
cluster: user_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/auth/reset-password
route:
cluster: user_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/auth/forgot-password/send
route:
cluster: email_api_cluster
timeout: 30s
rate_limits:
- actions:
- generic_key:
descriptor_value: forgot_password_send
- generic_key:
descriptor_key: "period"
descriptor_value: "minute"
- remote_address: {}
- actions:
- generic_key:
descriptor_value: forgot_password_send
- generic_key:
descriptor_key: "period"
descriptor_value: "hour"
- remote_address: {}
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: /api/users
route:
cluster: user_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: /api/v1/shop
route:
cluster: shop_api_cluster
timeout: 30s
- match:
prefix: /api/v1/player
route:
cluster: player_api_cluster
timeout: 30s
- match:
prefix: /api/v1/games
route:
cluster: game_api_cluster
timeout: 30s
- match:
prefix: /api/v1/games
headers:
- name: :method
exact_match: GET
route:
cluster: game_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
path: /api/v1/email/verification-code/send
route:
cluster: email_api_cluster
timeout: 30s
rate_limits:
- actions:
- generic_key:
descriptor_value: verify_code_send
- generic_key:
descriptor_key: "period"
descriptor_value: "minute"
- remote_address: {}
- actions:
- generic_key:
descriptor_value: verify_code_send
- generic_key:
descriptor_key: "period"
descriptor_value: "hour"
- remote_address: {}
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: /api/v1/wallet
route:
cluster: wallet_api_cluster
timeout: 30s
- match:
prefix: /api/v1/players
route:
cluster: player_api_cluster
timeout: 30s
- match:
prefix: /api/v1/orders
route:
cluster: order_api_cluster
timeout: 30s
- match:
prefix: /api/v1/email
route:
cluster: email_api_cluster
timeout: 30s
- match:
prefix: /api/v1/auth
route:
cluster: user_api_cluster
timeout: 30s
- match:
prefix: /api/v1/upload
route:
cluster: objectstory_api_cluster
timeout: 30s
- match:
prefix: /api/v1/files
route:
cluster: objectstory_api_cluster
timeout: 30s
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
- match:
prefix: /api/email
route:
cluster: email_api_cluster
timeout: 30s
- match:
prefix: /api/v1/game
route:
cluster: game_api_cluster
timeout: 30s
- match:
prefix: /api/v1
route:
cluster: user_api_cluster
timeout: 30s
- match:
prefix: /
direct_response:
status: 404
body:
inline_string: gateway route not found
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
json_format:
start_time: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
protocol: "%PROTOCOL%"
authority: "%REQ(:AUTHORITY)%"
user_agent: "%REQ(USER-AGENT)%"
request_id: "%REQ(X-REQUEST-ID)%"
response_code: "%RESPONSE_CODE%"
response_flags: "%RESPONSE_FLAGS%"
bytes_received: "%BYTES_RECEIVED%"
bytes_sent: "%BYTES_SENT%"
duration_ms: "%DURATION%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
upstream_host: "%UPSTREAM_HOST%"
upstream_service_time_ms: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"
route_name: "%ROUTE_NAME%"
http_filters:
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
local TOKEN_HEADER = "xsrf-token"
local TOKEN_COOKIE = "__Host-XSRF-TOKEN"
local GUARD_COOKIE = "__Host-XSRF-GUARD"
local seeded = false
local function seed_random()
if seeded then
return
end
seeded = true
math.randomseed(os.time())
end
local function split_cookie(header)
local out = {}
if not header then
return out
end
for pair in string.gmatch(header, "([^;]+)") do
local key, value = string.match(pair, "^%s*([^=]+)=?(.*)$")
if key ~= nil and value ~= nil then
out[string.lower(key)] = value
end
end
return out
end
local function is_safe_method(method)
return method == "GET" or method == "HEAD" or method == "OPTIONS"
end
local function build_token(request_id)
seed_random()
local rnd = tostring(math.random(100000, 999999))
local rid = request_id or "rid"
return tostring(os.time()) .. "-" .. rid .. "-" .. rnd
end
function envoy_on_request(request_handle)
local headers = request_handle:headers()
local method = headers:get(":method")
local cookie_header = headers:get("cookie")
local cookies = split_cookie(cookie_header)
local token_cookie = cookies[string.lower(TOKEN_COOKIE)]
local guard_cookie = cookies[string.lower(GUARD_COOKIE)]
request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_token_cookie", token_cookie == nil or token_cookie == "")
request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_guard_cookie", guard_cookie == nil or guard_cookie == "")
if token_cookie == nil or token_cookie == "" then
token_cookie = build_token(headers:get("x-request-id"))
request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", token_cookie)
else
request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", token_cookie)
end
if guard_cookie == nil or guard_cookie == "" then
guard_cookie = build_token(headers:get("x-request-id"))
request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", guard_cookie)
else
request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", guard_cookie)
end
if is_safe_method(method) then
return
end
local token_header = headers:get(TOKEN_HEADER)
if token_header == nil or token_header == "" then
request_handle:respond(
{[":status"] = "403", ["content-type"] = "application/json"},
'{"code":403,"message":"missing XSRF-TOKEN header"}'
)
return
end
if token_cookie == nil or token_cookie == "" or guard_cookie == nil or guard_cookie == "" then
request_handle:respond(
{[":status"] = "403", ["content-type"] = "application/json"},
'{"code":403,"message":"missing csrf cookies"}'
)
return
end
if token_header ~= token_cookie then
request_handle:respond(
{[":status"] = "403", ["content-type"] = "application/json"},
'{"code":403,"message":"xsrf token mismatch"}'
)
return
end
end
function envoy_on_response(response_handle)
local metadata = response_handle:streamInfo():dynamicMetadata():get("csrf")
if metadata == nil then
return
end
local token_value = metadata["token_value"]
local guard_value = metadata["guard_value"]
if metadata["need_set_token_cookie"] == true and token_value ~= nil and token_value ~= "" then
response_handle:headers():add(
"set-cookie",
TOKEN_COOKIE .. "=" .. token_value .. "; Path=/; Max-Age=7200; SameSite=Strict; Secure"
)
end
if metadata["need_set_guard_cookie"] == true and guard_value ~= nil and guard_value ~= "" then
response_handle:headers():add(
"set-cookie",
GUARD_COOKIE .. "=" .. guard_value .. "; Path=/; Max-Age=7200; SameSite=Strict; Secure; HttpOnly"
)
end
end
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
juwan_user_jwt:
issuer: juwan-user-rpc
from_cookies:
- JToken
local_jwks:
inline_string: '{"keys":[{"kty":"oct","k":"TUdVeU1XRTNaRGhqTVRRNVpEZzFNV1ZpT1dVME1HTTNPVEUyTldWa1lUQmxPVEU1WldSa1pEVTFZall6T0dKak9XUmlOek0wTlRjNE5ESXlNamxrWlE","alg":"HS256","use":"sig","kid":"juwan-hs256-1"}]}'
forward: false
claim_to_headers:
- header_name: x-auth-user-id
claim_name: UserId
- header_name: x-auth-is-admin
claim_name: IsAdmin
rules:
- match:
path: /healthz
- match:
prefix: /api/v1
headers:
- name: :method
exact_match: OPTIONS
- match:
path: /api/v1/auth/login
- match:
path: /api/v1/auth/register
- match:
path: /api/v1/auth/forgot-password
- match:
path: /api/v1/auth/reset-password
- match:
path: /api/v1/auth/forgot-password/send
- match:
path: /api/v1/email/verification-code/send
- match:
prefix: /api/v1
requires:
provider_name: juwan_user_jwt
- match:
prefix: /api/users
requires:
provider_name: juwan_user_jwt
- match:
prefix: /api/email
requires:
provider_name: juwan_user_jwt
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
failure_mode_allow: false
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
grpc_service:
envoy_grpc:
cluster_name: authz_adapter_cluster
timeout: 0.5s
# RLS 全局过滤器
- name: envoy.filters.http.ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
domain: api
failure_mode_deny: false
rate_limited_as_resource_exhausted: true
enable_x_ratelimit_headers: DRAFT_VERSION_03
rate_limit_service:
transport_api_version: V3
grpc_service:
envoy_grpc:
cluster_name: ratelimit_cluster
timeout: 0.2s
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: user_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: user_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: user-api
port_value: 8888
- name: email_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: email_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: email-api
port_value: 8888
- name: shop_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: shop_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: shop-api
port_value: 8888
- name: game_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: game_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: game-api
port_value: 8888
- name: objectstory_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: objectstory_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: objectstory-api
port_value: 8888
- name: wallet_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: wallet_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: wallet-api
port_value: 8888
- name: order_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: order_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: order-api
port_value: 8888
- name: player_api_cluster
connect_timeout: 2s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: player_api_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: player-api
port_value: 8888
- name: authz_adapter_cluster
connect_timeout: 0.5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: { }
load_assignment:
cluster_name: authz_adapter_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: authz-adapter
port_value: 9002
# RLS 集群
- name: ratelimit_cluster
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: ratelimit_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ratelimit # RLS 地址
port_value: 8081 # RLS gRPC 端口
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
+33
View File
@@ -0,0 +1,33 @@
domain: api
descriptors:
- key: generic_key
value: login
descriptors:
- key: remote_address
rate_limit:
unit: MINUTE
requests_per_unit: 10
- key: generic_key
value: register
descriptors:
- key: remote_address
rate_limit:
unit: MINUTE
requests_per_unit: 5
- key: generic_key
value: forgot_password_send
descriptors:
- key: remote_address
rate_limit:
unit: MINUTE
requests_per_unit: 3
- key: generic_key
value: verify_code_send
descriptors:
- key: remote_address
rate_limit:
unit: MINUTE
requests_per_unit: 3
+4 -4
View File
@@ -1,4 +1,4 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: monitoring name: monitoring
+82 -82
View File
@@ -1,82 +1,82 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: grafana-admin name: grafana-admin
namespace: monitoring namespace: monitoring
type: Opaque type: Opaque
data: data:
admin-user: YWRtaW4= admin-user: YWRtaW4=
admin-password: Y2hhbmdlLW1l admin-password: Y2hhbmdlLW1l
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: grafana-datasources name: grafana-datasources
namespace: monitoring namespace: monitoring
data: data:
datasources.yaml: | datasources.yaml: |
apiVersion: 1 apiVersion: 1
datasources: datasources:
- name: Prometheus - name: Prometheus
type: prometheus type: prometheus
access: proxy access: proxy
url: http://prometheus:9090 url: http://prometheus:9090
isDefault: true isDefault: true
- name: Loki - name: Loki
type: loki type: loki
access: proxy access: proxy
url: http://loki:3100 url: http://loki:3100
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: grafana name: grafana
namespace: monitoring namespace: monitoring
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: grafana app: grafana
template: template:
metadata: metadata:
labels: labels:
app: grafana app: grafana
spec: spec:
containers: containers:
- name: grafana - name: grafana
image: grafana/grafana:10.4.6 image: grafana/grafana:10.4.6
ports: ports:
- name: http - name: http
containerPort: 3000 containerPort: 3000
env: env:
- name: GF_SECURITY_ADMIN_USER - name: GF_SECURITY_ADMIN_USER
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: grafana-admin name: grafana-admin
key: admin-user key: admin-user
- name: GF_SECURITY_ADMIN_PASSWORD - name: GF_SECURITY_ADMIN_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: grafana-admin name: grafana-admin
key: admin-password key: admin-password
volumeMounts: volumeMounts:
- name: datasources - name: datasources
mountPath: /etc/grafana/provisioning/datasources mountPath: /etc/grafana/provisioning/datasources
volumes: volumes:
- name: datasources - name: datasources
configMap: configMap:
name: grafana-datasources name: grafana-datasources
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: grafana name: grafana
namespace: monitoring namespace: monitoring
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: http - name: http
port: 3000 port: 3000
targetPort: http targetPort: http
selector: selector:
app: grafana app: grafana
+90 -90
View File
@@ -1,90 +1,90 @@
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: loki-config name: loki-config
namespace: monitoring namespace: monitoring
data: data:
loki.yaml: | loki.yaml: |
auth_enabled: false auth_enabled: false
server: server:
http_listen_port: 3100 http_listen_port: 3100
common: common:
path_prefix: /loki path_prefix: /loki
storage: storage:
filesystem: filesystem:
chunks_directory: /loki/chunks chunks_directory: /loki/chunks
rules_directory: /loki/rules rules_directory: /loki/rules
replication_factor: 1 replication_factor: 1
ring: ring:
kvstore: kvstore:
store: inmemory store: inmemory
schema_config: schema_config:
configs: configs:
- from: 2024-01-01 - from: 2024-01-01
store: boltdb-shipper store: boltdb-shipper
object_store: filesystem object_store: filesystem
schema: v12 schema: v12
index: index:
prefix: index_ prefix: index_
period: 24h period: 24h
storage_config: storage_config:
boltdb_shipper: boltdb_shipper:
active_index_directory: /loki/index active_index_directory: /loki/index
cache_location: /loki/cache cache_location: /loki/cache
shared_store: filesystem shared_store: filesystem
ruler: ruler:
alertmanager_url: http://localhost:9093 alertmanager_url: http://localhost:9093
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: loki name: loki
namespace: monitoring namespace: monitoring
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: loki app: loki
template: template:
metadata: metadata:
labels: labels:
app: loki app: loki
spec: spec:
containers: containers:
- name: loki - name: loki
image: grafana/loki:2.9.6 image: grafana/loki:2.9.6
args: args:
- "-config.file=/etc/loki/loki.yaml" - "-config.file=/etc/loki/loki.yaml"
ports: ports:
- name: http - name: http
containerPort: 3100 containerPort: 3100
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /etc/loki mountPath: /etc/loki
- name: data - name: data
mountPath: /loki mountPath: /loki
volumes: volumes:
- name: config - name: config
configMap: configMap:
name: loki-config name: loki-config
- name: data - name: data
emptyDir: {} emptyDir: {}
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: loki name: loki
namespace: monitoring namespace: monitoring
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: http - name: http
port: 3100 port: 3100
targetPort: http targetPort: http
selector: selector:
app: loki app: loki
+138 -138
View File
@@ -1,138 +1,138 @@
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: prometheus name: prometheus
namespace: monitoring namespace: monitoring
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: prometheus name: prometheus
rules: rules:
- apiGroups: [""] - apiGroups: [""]
resources: resources:
- nodes - nodes
- nodes/metrics - nodes/metrics
- services - services
- endpoints - endpoints
- pods - pods
- namespaces - namespaces
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "apps"] - apiGroups: ["extensions", "apps"]
resources: resources:
- deployments - deployments
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"] - nonResourceURLs: ["/metrics"]
verbs: ["get"] verbs: ["get"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: prometheus name: prometheus
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: ClusterRole kind: ClusterRole
name: prometheus name: prometheus
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: prometheus name: prometheus
namespace: monitoring namespace: monitoring
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: prometheus-config name: prometheus-config
namespace: monitoring namespace: monitoring
data: data:
prometheus.yml: | prometheus.yml: |
global: global:
scrape_interval: 15s scrape_interval: 15s
evaluation_interval: 15s evaluation_interval: 15s
scrape_configs: scrape_configs:
- job_name: "prometheus" - job_name: "prometheus"
static_configs: static_configs:
- targets: ["localhost:9090"] - targets: ["localhost:9090"]
- job_name: "kubernetes-annotated-endpoints" - job_name: "kubernetes-annotated-endpoints"
kubernetes_sd_configs: kubernetes_sd_configs:
- role: endpoints - role: endpoints
relabel_configs: relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep action: keep
regex: "true" regex: "true"
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace action: replace
target_label: __scheme__ target_label: __scheme__
regex: (https?) regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace action: replace
target_label: __metrics_path__ target_label: __metrics_path__
regex: (.+) regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace action: replace
target_label: __address__ target_label: __address__
regex: (.+):(?:\d+);(\d+) regex: (.+):(?:\d+);(\d+)
replacement: $1:$2 replacement: $1:$2
- source_labels: [__meta_kubernetes_namespace] - source_labels: [__meta_kubernetes_namespace]
action: replace action: replace
target_label: namespace target_label: namespace
- source_labels: [__meta_kubernetes_service_name] - source_labels: [__meta_kubernetes_service_name]
action: replace action: replace
target_label: service target_label: service
- source_labels: [__meta_kubernetes_endpoint_port_name] - source_labels: [__meta_kubernetes_endpoint_port_name]
action: replace action: replace
target_label: port target_label: port
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: prometheus name: prometheus
namespace: monitoring namespace: monitoring
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: prometheus app: prometheus
template: template:
metadata: metadata:
labels: labels:
app: prometheus app: prometheus
spec: spec:
serviceAccountName: prometheus serviceAccountName: prometheus
containers: containers:
- name: prometheus - name: prometheus
image: prom/prometheus:v2.53.0 image: prom/prometheus:v2.53.0
args: args:
- "--config.file=/etc/prometheus/prometheus.yml" - "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus" - "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=15d" - "--storage.tsdb.retention.time=15d"
- "--web.enable-lifecycle" - "--web.enable-lifecycle"
ports: ports:
- name: http - name: http
containerPort: 9090 containerPort: 9090
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /etc/prometheus mountPath: /etc/prometheus
- name: data - name: data
mountPath: /prometheus mountPath: /prometheus
volumes: volumes:
- name: config - name: config
configMap: configMap:
name: prometheus-config name: prometheus-config
- name: data - name: data
emptyDir: {} emptyDir: {}
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: prometheus name: prometheus
namespace: monitoring namespace: monitoring
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: http - name: http
port: 9090 port: 9090
targetPort: http targetPort: http
selector: selector:
app: prometheus app: prometheus
+149 -149
View File
@@ -1,149 +1,149 @@
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: promtail name: promtail
namespace: monitoring namespace: monitoring
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: promtail name: promtail
rules: rules:
- apiGroups: [""] - apiGroups: [""]
resources: resources:
- nodes - nodes
- pods - pods
- pods/log - pods/log
- services - services
- endpoints - endpoints
- namespaces - namespaces
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: promtail name: promtail
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: ClusterRole kind: ClusterRole
name: promtail name: promtail
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: promtail name: promtail
namespace: monitoring namespace: monitoring
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: promtail-config name: promtail-config
namespace: monitoring namespace: monitoring
data: data:
promtail.yaml: | promtail.yaml: |
server: server:
http_listen_port: 9080 http_listen_port: 9080
grpc_listen_port: 0 grpc_listen_port: 0
positions: positions:
filename: /run/promtail/positions.yaml filename: /run/promtail/positions.yaml
clients: clients:
- url: http://loki:3100/loki/api/v1/push - url: http://loki:3100/loki/api/v1/push
scrape_configs: scrape_configs:
- job_name: kubernetes-pods - job_name: kubernetes-pods
kubernetes_sd_configs: kubernetes_sd_configs:
- role: pod - role: pod
relabel_configs: relabel_configs:
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
target_label: app target_label: app
regex: (.+) regex: (.+)
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_label_app] source_labels: [__meta_kubernetes_pod_label_app]
target_label: app target_label: app
regex: (.+) regex: (.+)
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_node_name] source_labels: [__meta_kubernetes_pod_node_name]
target_label: node target_label: node
- action: replace - action: replace
source_labels: [__meta_kubernetes_namespace] source_labels: [__meta_kubernetes_namespace]
target_label: namespace target_label: namespace
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_name] source_labels: [__meta_kubernetes_pod_name]
target_label: pod target_label: pod
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_container_name] source_labels: [__meta_kubernetes_pod_container_name]
target_label: container target_label: container
- action: replace - action: replace
source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name] source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
separator: / separator: /
target_label: __path__ target_label: __path__
replacement: /var/log/pods/*$1/*.log replacement: /var/log/pods/*$1/*.log
- job_name: kubernetes-pods-static - job_name: kubernetes-pods-static
pipeline_stages: pipeline_stages:
- regex: - regex:
source: filename source: filename
expression: /var/log/pods/(?P<namespace>[^_]+)_(?P<pod>[^_]+)_[^/]+/(?P<container>[^/]+)/[0-9]+\.log expression: /var/log/pods/(?P<namespace>[^_]+)_(?P<pod>[^_]+)_[^/]+/(?P<container>[^/]+)/[0-9]+\.log
- regex: - regex:
source: pod source: pod
expression: ^(?P<app>.+?)(?:-[a-f0-9]{8,10}-[a-z0-9]{5}|-[0-9]+)?$ expression: ^(?P<app>.+?)(?:-[a-f0-9]{8,10}-[a-z0-9]{5}|-[0-9]+)?$
- labels: - labels:
namespace: namespace:
pod: pod:
container: container:
app: app:
static_configs: static_configs:
- targets: - targets:
- localhost - localhost
labels: labels:
job: kubernetes-pods job: kubernetes-pods
__path__: /var/log/pods/*/*/*.log __path__: /var/log/pods/*/*/*.log
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: DaemonSet kind: DaemonSet
metadata: metadata:
name: promtail name: promtail
namespace: monitoring namespace: monitoring
spec: spec:
selector: selector:
matchLabels: matchLabels:
app: promtail app: promtail
template: template:
metadata: metadata:
labels: labels:
app: promtail app: promtail
spec: spec:
serviceAccountName: promtail serviceAccountName: promtail
tolerations: tolerations:
- operator: "Exists" - operator: "Exists"
containers: containers:
- name: promtail - name: promtail
image: grafana/promtail:2.9.6 image: grafana/promtail:2.9.6
securityContext: securityContext:
runAsUser: 0 runAsUser: 0
runAsGroup: 0 runAsGroup: 0
args: args:
- "-config.file=/etc/promtail/promtail.yaml" - "-config.file=/etc/promtail/promtail.yaml"
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /etc/promtail mountPath: /etc/promtail
- name: positions - name: positions
mountPath: /run/promtail mountPath: /run/promtail
- name: varlog - name: varlog
mountPath: /var/log mountPath: /var/log
readOnly: true readOnly: true
- name: dockercontainers - name: dockercontainers
mountPath: /var/lib/docker/containers mountPath: /var/lib/docker/containers
readOnly: true readOnly: true
volumes: volumes:
- name: config - name: config
configMap: configMap:
name: promtail-config name: promtail-config
- name: positions - name: positions
emptyDir: {} emptyDir: {}
- name: varlog - name: varlog
hostPath: hostPath:
path: /var/log path: /var/log
- name: dockercontainers - name: dockercontainers
hostPath: hostPath:
path: /var/lib/docker/containers path: /var/lib/docker/containers
+67 -67
View File
@@ -1,67 +1,67 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: jwt-secret name: jwt-secret
namespace: juwan namespace: juwan
type: Opaque type: Opaque
data: data:
secret-key: MGUyMWE3ZDhjMTQ5ZDg1MWViOWU0MGM3OTE2NWVkYTBlOTE5ZWRkZDU1YjYzOGJjOWRiNzM0NTc4NDIyMjlkZQ== secret-key: MGUyMWE3ZDhjMTQ5ZDg1MWViOWU0MGM3OTE2NWVkYTBlOTE5ZWRkZDU1YjYzOGJjOWRiNzM0NTc4NDIyMjlkZQ==
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: envoy-gateway name: envoy-gateway
namespace: juwan namespace: juwan
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: jwt-secret-reader name: jwt-secret-reader
namespace: juwan namespace: juwan
rules: rules:
# JWT Secret 读取权限 # JWT Secret 读取权限
- apiGroups: [""] - apiGroups: [""]
resources: ["secrets"] resources: ["secrets"]
resourceNames: ["jwt-secret"] resourceNames: ["jwt-secret"]
verbs: ["get"] verbs: ["get"]
# 服务发现权限 # 服务发现权限
- apiGroups: [""] - apiGroups: [""]
resources: ["endpoints"] resources: ["endpoints"]
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"] - apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"] resources: ["endpointslices"]
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: user-rpc-jwt-secret-reader name: user-rpc-jwt-secret-reader
namespace: juwan namespace: juwan
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: Role kind: Role
name: jwt-secret-reader name: jwt-secret-reader
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: envoy-gateway-jwt-secret-reader name: envoy-gateway-jwt-secret-reader
namespace: juwan namespace: juwan
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: Role kind: Role
name: jwt-secret-reader name: jwt-secret-reader
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: envoy-gateway name: envoy-gateway
namespace: juwan namespace: juwan
+1032 -1032
View File
File diff suppressed because it is too large Load Diff
+108 -108
View File
@@ -1,108 +1,108 @@
# Envoy Gateway Configuration # Envoy Gateway Configuration
This document explains how the Envoy unified ingress gateway is configured and how to modify it. This document explains how the Envoy unified ingress gateway is configured and how to modify it.
## Files ## Files
- deploy/k8s/envoy/envoy.yaml: ConfigMap + Deployment + Service for Envoy - deploy/k8s/envoy/envoy.yaml: ConfigMap + Deployment + Service for Envoy
## Current Behavior ## Current Behavior
- Envoy listens on port 8080 in the Pod and exposes port 80 via a ClusterIP Service. - Envoy listens on port 8080 in the Pod and exposes port 80 via a ClusterIP Service.
- Route `/api/users` to `user-api-svc:8888`. - Route `/api/users` to `user-api-svc:8888`.
- Route `/api/email` to `email-api-svc:8888`. - Route `/api/email` to `email-api-svc:8888`.
- Route `/healthz` returns `200 ok` directly from gateway. - Route `/healthz` returns `200 ok` directly from gateway.
- Unknown routes return `404` from gateway. - Unknown routes return `404` from gateway.
## Routing ## Routing
In envoy.yaml, routes are defined under: In envoy.yaml, routes are defined under:
static_resources -> listeners -> http_connection_manager -> route_config -> virtual_hosts static_resources -> listeners -> http_connection_manager -> route_config -> virtual_hosts
The current routing rules are: The current routing rules are:
- `prefix: /api/users` -> `cluster: user_api_cluster` - `prefix: /api/users` -> `cluster: user_api_cluster`
- `prefix: /api/email` -> `cluster: email_api_cluster` - `prefix: /api/email` -> `cluster: email_api_cluster`
- `path: /healthz` -> direct response `200` - `path: /healthz` -> direct response `200`
- `prefix: /` -> direct response `404` - `prefix: /` -> direct response `404`
To add a new HTTP service, add a new route above the default route and define a new cluster. To add a new HTTP service, add a new route above the default route and define a new cluster.
Example: route `/api/order` to `order-api-svc:8899` Example: route `/api/order` to `order-api-svc:8899`
1) Add a route match: 1) Add a route match:
- match: - match:
prefix: "/api/order" prefix: "/api/order"
route: route:
cluster: order_api_cluster cluster: order_api_cluster
1) Add a cluster: 1) Add a cluster:
- name: order_api_cluster - name: order_api_cluster
connect_timeout: 2s connect_timeout: 2s
type: STRICT_DNS type: STRICT_DNS
lb_policy: ROUND_ROBIN lb_policy: ROUND_ROBIN
load_assignment: load_assignment:
cluster_name: order_api_cluster cluster_name: order_api_cluster
endpoints: endpoints:
- lb_endpoints: - lb_endpoints:
- endpoint: - endpoint:
address: address:
socket_address: socket_address:
address: order-api-svc.juwan.svc.cluster.local address: order-api-svc.juwan.svc.cluster.local
port_value: 8899 port_value: 8899
## CSRF Protection (Double Cookie) ## CSRF Protection (Double Cookie)
Envoy uses a Lua filter for double-cookie CSRF validation: Envoy uses a Lua filter for double-cookie CSRF validation:
- Safe methods (GET/HEAD/OPTIONS): - Safe methods (GET/HEAD/OPTIONS):
- If missing, Envoy auto-issues two cookies: - If missing, Envoy auto-issues two cookies:
- `csrf_token` - `csrf_token`
- `csrf_guard` - `csrf_guard`
- Unsafe methods (POST/PUT/PATCH/DELETE, etc): - Unsafe methods (POST/PUT/PATCH/DELETE, etc):
- Requires BOTH headers: - Requires BOTH headers:
- `X-CSRF-Token` - `X-CSRF-Token`
- `X-CSRF-Guard` - `X-CSRF-Guard`
- Requires BOTH cookies: - Requires BOTH cookies:
- `csrf_token` - `csrf_token`
- `csrf_guard` - `csrf_guard`
- Header values must exactly match cookie values, otherwise Envoy returns `403`. - Header values must exactly match cookie values, otherwise Envoy returns `403`.
If you want different cookie or header names, update these constants in Lua: If you want different cookie or header names, update these constants in Lua:
- `TOKEN_COOKIE` - `TOKEN_COOKIE`
- `GUARD_COOKIE` - `GUARD_COOKIE`
- `TOKEN_HEADER` - `TOKEN_HEADER`
- `GUARD_HEADER` - `GUARD_HEADER`
To relax or tighten rules, edit the functions: To relax or tighten rules, edit the functions:
- is_safe(method) - is_safe(method)
- envoy_on_request(request_handle) - envoy_on_request(request_handle)
## Cookie Attributes ## Cookie Attributes
Current Set-Cookie: Current Set-Cookie:
- `csrf_token=<value>; Path=/; SameSite=Strict` - `csrf_token=<value>; Path=/; SameSite=Strict`
- `csrf_guard=<value>; Path=/; SameSite=Strict` - `csrf_guard=<value>; Path=/; SameSite=Strict`
## Deployment ## Deployment
Apply or update: Apply or update:
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
## Common Changes ## Common Changes
- Change listening port: - Change listening port:
- Update listener port_value and Service targetPort/port. - Update listener port_value and Service targetPort/port.
- Change service namespace: - Change service namespace:
- Update cluster DNS addresses (e.g. `service.ns.svc.cluster.local`). - Update cluster DNS addresses (e.g. `service.ns.svc.cluster.local`).
- Add more services: - Add more services:
- Add route + add cluster, as shown above. - Add route + add cluster, as shown above.
- Update CSRF policy: - Update CSRF policy:
- Edit Lua validation logic in `envoy.filters.http.lua`. - Edit Lua validation logic in `envoy.filters.http.lua`.
+385 -385
View File
@@ -1,385 +1,385 @@
# Kubernetes 部署问题排查与解决记录 # Kubernetes 部署问题排查与解决记录
**日期**: 2026年2月23日 **日期**: 2026年2月23日
**问题**: user-rpc 和 Redis 部署失败 **问题**: user-rpc 和 Redis 部署失败
**状态**: 已诊断,解决中 **状态**: 已诊断,解决中
--- ---
## 📋 问题描述 ## 📋 问题描述
执行 `kubectl apply -f test.yaml` 后,资源虽然创建成功,但实际的应用 pods 并未正常运行: 执行 `kubectl apply -f test.yaml` 后,资源虽然创建成功,但实际的应用 pods 并未正常运行:
``` ```
kubectl apply -f ..\test.yaml kubectl apply -f ..\test.yaml
✓ deployment.apps/user-rpc created ✓ deployment.apps/user-rpc created
✓ service/user-rpc-svc created ✓ service/user-rpc-svc created
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-c created ✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-c created
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-m created ✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-m created
✓ redisreplication.redis.redis.opstreelabs.in/user-redis created ✓ redisreplication.redis.redis.opstreelabs.in/user-redis created
✓ redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created ✓ redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created
✓ cluster.postgresql.cnpg.io/user-db created ✓ cluster.postgresql.cnpg.io/user-db created
``` ```
但执行 `kubectl get all` 后,发现: 但执行 `kubectl get all` 后,发现:
-**user-rpc pods 未创建**Deployment 0/3 replicas ready -**user-rpc pods 未创建**Deployment 0/3 replicas ready
-**Redis pods 未创建**RedisReplication 资源存在但无 pods -**Redis pods 未创建**RedisReplication 资源存在但无 pods
- ✅ user-db pods 正常运行(3/3 - ✅ user-db pods 正常运行(3/3
--- ---
## 🔍 排查过程 ## 🔍 排查过程
### 第一步:检查 Deployment 状态 ### 第一步:检查 Deployment 状态
```bash ```bash
kubectl describe deployment user-rpc kubectl describe deployment user-rpc
``` ```
**发现** **发现**
``` ```
Conditions: Conditions:
Type Status Reason Type Status Reason
---- ------ ------ ---- ------ ------
Progressing True NewReplicaSetCreated Progressing True NewReplicaSetCreated
Available False MinimumReplicasUnavailable Available False MinimumReplicasUnavailable
ReplicaFailure True FailedCreate ReplicaFailure True FailedCreate
``` ```
### 第二步:检查 ReplicaSet 详情 ### 第二步:检查 ReplicaSet 详情
```bash ```bash
kubectl describe replicaset user-rpc-6bf77fbcd9 kubectl describe replicaset user-rpc-6bf77fbcd9
``` ```
**发现关键错误** **发现关键错误**
``` ```
Events: Events:
Type Reason Age From Message Type Reason Age From Message
---- ------ ---- ---- ------- ---- ------ ---- ---- -------
Warning FailedCreate 3m53s replicaset-controller Error creating: Warning FailedCreate 3m53s replicaset-controller Error creating:
pods "user-rpc-6bf77fbcd9-" is forbidden: error looking up service pods "user-rpc-6bf77fbcd9-" is forbidden: error looking up service
account default/find-endpoints: serviceaccount "find-endpoints" not found account default/find-endpoints: serviceaccount "find-endpoints" not found
``` ```
**问题 #1 诊断完成**:❌ **缺失 ServiceAccount "find-endpoints"** **问题 #1 诊断完成**:❌ **缺失 ServiceAccount "find-endpoints"**
### 第三步:检查现有 ServiceAccounts ### 第三步:检查现有 ServiceAccounts
```bash ```bash
kubectl get serviceaccount kubectl get serviceaccount
``` ```
**结果** **结果**
``` ```
NAME AGE NAME AGE
cluster-example 4d10h cluster-example 4d10h
default 13d default 13d
redis-operator 9h redis-operator 9h
user-db 4m9s user-db 4m9s
``` ```
确认 `find-endpoints` 不存在。 确认 `find-endpoints` 不存在。
### 第四步:检查 Secrets ### 第四步:检查 Secrets
```bash ```bash
kubectl get secrets kubectl get secrets
``` ```
**结果**:默认 secrets 都存在,包括: **结果**:默认 secrets 都存在,包括:
- ✅ user-db-app - ✅ user-db-app
- ✅ user-redis - ✅ user-redis
- ✅ user-db-ca, user-db-replication, user-db-server - ✅ user-db-ca, user-db-replication, user-db-server
### 第五步:检查 Redis 部署 ### 第五步:检查 Redis 部署
```bash ```bash
kubectl get redisreplication kubectl get redisreplication
kubectl get pods | grep redis kubectl get pods | grep redis
``` ```
**发现** **发现**
- ✅ RedisReplication 资源存在 - ✅ RedisReplication 资源存在
- ❌ Redis pods **完全没有被创建** - ❌ Redis pods **完全没有被创建**
**问题 #2 诊断**:❌ **Redis Operator 未响应 RedisReplication 资源** **问题 #2 诊断**:❌ **Redis Operator 未响应 RedisReplication 资源**
--- ---
## 🔧 第一次修复尝试 ## 🔧 第一次修复尝试
### 创建缺失的 ServiceAccount ### 创建缺失的 ServiceAccount
```bash ```bash
kubectl create serviceaccount find-endpoints kubectl create serviceaccount find-endpoints
``` ```
**结果**:✅ ServiceAccount 创建成功 **结果**:✅ ServiceAccount 创建成功
### 重启 Deployment ### 重启 Deployment
```bash ```bash
kubectl rollout restart deployment user-rpc kubectl rollout restart deployment user-rpc
``` ```
**等待 5-10 秒后重新检查** **等待 5-10 秒后重新检查**
```bash ```bash
kubectl get pods -o wide kubectl get pods -o wide
``` ```
**新的发现** **新的发现**
``` ```
NAME READY STATUS RESTARTS AGE NAME READY STATUS RESTARTS AGE
user-rpc-66f97fbdcc-ws7rc 0/1 ErrImagePull 0 26s user-rpc-66f97fbdcc-ws7rc 0/1 ErrImagePull 0 26s
user-rpc-6bf77fbcd9-njm2z 0/1 ErrImagePull 0 29s user-rpc-6bf77fbcd9-njm2z 0/1 ErrImagePull 0 29s
user-rpc-6bf77fbcd9-nwjtw 0/1 ImagePullBackOff 0 29s user-rpc-6bf77fbcd9-nwjtw 0/1 ImagePullBackOff 0 29s
user-rpc-6bf77fbcd9-wjrf8 0/1 ErrImagePull 0 29s user-rpc-6bf77fbcd9-wjrf8 0/1 ErrImagePull 0 29s
``` ```
**好消息**:Pods 现在被创建了!(说明 ServiceAccount 问题已解决) **好消息**:Pods 现在被创建了!(说明 ServiceAccount 问题已解决)
**新问题**:镜像拉取失败 **新问题**:镜像拉取失败
--- ---
## 🐛 根因分析 ## 🐛 根因分析
### 问题 #1:缺失 ServiceAccount ✅ 已解决 ### 问题 #1:缺失 ServiceAccount ✅ 已解决
**根本原因**test.yaml 的 Deployment manifest 指定了: **根本原因**test.yaml 的 Deployment manifest 指定了:
```yaml ```yaml
spec: spec:
template: template:
spec: spec:
serviceAccountName: find-endpoints # 这个 ServiceAccount 不存在 serviceAccountName: find-endpoints # 这个 ServiceAccount 不存在
``` ```
但没有在 test.yaml 中创建 ServiceAccount 资源。 但没有在 test.yaml 中创建 ServiceAccount 资源。
**解决方案** **解决方案**
```bash ```bash
kubectl create serviceaccount find-endpoints kubectl create serviceaccount find-endpoints
``` ```
或在 test.yaml 中添加: 或在 test.yaml 中添加:
```yaml ```yaml
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: find-endpoints name: find-endpoints
namespace: default namespace: default
``` ```
--- ---
### 问题 #2:镜像拉取失败 ❌ 需要修复 ### 问题 #2:镜像拉取失败 ❌ 需要修复
```bash ```bash
kubectl describe pod user-rpc-6bf77fbcd9-njm2z kubectl describe pod user-rpc-6bf77fbcd9-njm2z
``` ```
**详细错误日志** **详细错误日志**
``` ```
Events: Events:
Warning Failed 38s kubelet Failed to pull image Warning Failed 38s kubelet Failed to pull image
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...": "103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
Error response from daemon: Get "https://103.236.53.208:4418/v2/": Error response from daemon: Get "https://103.236.53.208:4418/v2/":
context deadline exceeded (Client.Timeout exceeded while awaiting headers) context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Warning Failed 23s kubelet Failed to pull image Warning Failed 23s kubelet Failed to pull image
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...": "103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
http: server gave HTTP response to HTTPS client http: server gave HTTP response to HTTPS client
``` ```
**根本原因分析** **根本原因分析**
1. **网络连接失败**`context deadline exceeded` - 无法连接到镜像仓库 1. **网络连接失败**`context deadline exceeded` - 无法连接到镜像仓库
2. **协议不匹配**`http: server gave HTTP response to HTTPS client` - 2. **协议不匹配**`http: server gave HTTP response to HTTPS client` -
- 地址 `103.236.53.208:4418` 应该是 HTTP 而不是 HTTPS - 地址 `103.236.53.208:4418` 应该是 HTTP 而不是 HTTPS
- Docker daemon 尝试用 HTTPS 连接,但服务器使用 HTTP - Docker daemon 尝试用 HTTPS 连接,但服务器使用 HTTP
**可能原因** **可能原因**
- 镜像仓库地址错误或不可访问 - 镜像仓库地址错误或不可访问
- 镜像仓库需要特定的网络配置 - 镜像仓库需要特定的网络配置
- 仓库服务器离线或配置不当 - 仓库服务器离线或配置不当
--- ---
### 问题 #3:Redis 部署失败 ❌ 需要诊断 ### 问题 #3:Redis 部署失败 ❌ 需要诊断
**现象** **现象**
- RedisReplication 和 RedisSentinel CRD 资源创建成功 - RedisReplication 和 RedisSentinel CRD 资源创建成功
- 但没有对应的 Redis pods 被创建 - 但没有对应的 Redis pods 被创建
- `kubectl get pods | grep redis` 返回空 - `kubectl get pods | grep redis` 返回空
**可能原因** **可能原因**
1. **Redis Operator 未正常工作** 1. **Redis Operator 未正常工作**
- Operator pod 可能存在问题 - Operator pod 可能存在问题
- Operator 未能监听到新的 RedisReplication 资源 - Operator 未能监听到新的 RedisReplication 资源
2. **CRD 或 API 版本问题** 2. **CRD 或 API 版本问题**
- manifest 中使用的 API 版本 `v1beta2` 可能不匹配 Operator 版本 - manifest 中使用的 API 版本 `v1beta2` 可能不匹配 Operator 版本
3. **资源限制或权限问题** 3. **资源限制或权限问题**
- Operator 无权限创建 pods - Operator 无权限创建 pods
- 集群资源限制阻止了 pod 创建 - 集群资源限制阻止了 pod 创建
--- ---
## ✅ 已执行的修复 ## ✅ 已执行的修复
| # | 问题 | 修复方法 | 状态 | | # | 问题 | 修复方法 | 状态 |
|---|------|---------|------| |---|------|---------|------|
| 1 | 缺失 ServiceAccount | `kubectl create serviceaccount find-endpoints` | ✅ 完成 | | 1 | 缺失 ServiceAccount | `kubectl create serviceaccount find-endpoints` | ✅ 完成 |
| 2 | 镜像拉取失败 | 需要更新镜像地址或修复网络 | ⏳ 待处理 | | 2 | 镜像拉取失败 | 需要更新镜像地址或修复网络 | ⏳ 待处理 |
| 3 | Redis pods 未创建 | 需要诊断 Operator 日志 | ⏳ 待诊断 | | 3 | Redis pods 未创建 | 需要诊断 Operator 日志 | ⏳ 待诊断 |
--- ---
## 🚀 下一步解决方案 ## 🚀 下一步解决方案
### 优先级 1:修复 user-rpc 镜像拉取 ### 优先级 1:修复 user-rpc 镜像拉取
**选项 A:使用本地/内部镜像** **选项 A:使用本地/内部镜像**
```yaml ```yaml
# 修改 test.yaml 中的镜像地址 # 修改 test.yaml 中的镜像地址
image: localhost:5000/user-rpc:latest # 本地私有仓库 image: localhost:5000/user-rpc:latest # 本地私有仓库
# 或 # 或
image: user-rpc:latest # 本地镜像(如果已通过 docker load 导入) image: user-rpc:latest # 本地镜像(如果已通过 docker load 导入)
``` ```
**选项 B:修复仓库地址** **选项 B:修复仓库地址**
```yaml ```yaml
# 如果 103.236.53.208:4418 确实是正确仓库 # 如果 103.236.53.208:4418 确实是正确仓库
image: http://103.236.53.208:4418/library/user-rpc:latest # 显式使用 HTTP image: http://103.236.53.208:4418/library/user-rpc:latest # 显式使用 HTTP
``` ```
**验证步骤** **验证步骤**
```bash ```bash
# 检查镜像仓库连接性 # 检查镜像仓库连接性
curl -v http://103.236.53.208:4418/v2/ curl -v http://103.236.53.208:4418/v2/
``` ```
### 优先级 2:诊断 Redis Operator ### 优先级 2:诊断 Redis Operator
```bash ```bash
# 查看 Operator 日志 # 查看 Operator 日志
kubectl logs -l app.kubernetes.io/name=redis-operator -f kubectl logs -l app.kubernetes.io/name=redis-operator -f
# 查看 Operator pod # 查看 Operator pod
kubectl get pods -A | grep redis-operator kubectl get pods -A | grep redis-operator
# 查看 RedisReplication 详细信息 # 查看 RedisReplication 详细信息
kubectl describe redisreplication user-redis kubectl describe redisreplication user-redis
# 检查 Operator 权限(RBAC # 检查 Operator 权限(RBAC
kubectl get role,rolebinding,clusterrole,clusterrolebinding | grep redis kubectl get role,rolebinding,clusterrole,clusterrolebinding | grep redis
``` ```
### 优先级 3:增强 test.yaml ### 优先级 3:增强 test.yaml
建议在 test.yaml 中添加缺失的资源定义: 建议在 test.yaml 中添加缺失的资源定义:
```yaml ```yaml
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: find-endpoints name: find-endpoints
namespace: default namespace: default
--- ---
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: registry-credentials name: registry-credentials
namespace: default namespace: default
type: kubernetes.io/dockercfg type: kubernetes.io/dockercfg
data: data:
.dockercfg: <base64-encoded-credentials> # 如果需要私有仓库认证 .dockercfg: <base64-encoded-credentials> # 如果需要私有仓库认证
``` ```
--- ---
## 📊 当前集群状态 ## 📊 当前集群状态
### Pods 状态总结 ### Pods 状态总结
| 应用 | 期望副本 | 实际运行 | 状态 | | 应用 | 期望副本 | 实际运行 | 状态 |
|------|---------|---------|------| |------|---------|---------|------|
| user-db | 3 | 3 | ✅ 正常 | | user-db | 3 | 3 | ✅ 正常 |
| user-rpc | 3 | 0 | ❌ 镜像拉取失败 | | user-rpc | 3 | 0 | ❌ 镜像拉取失败 |
| Redis | 3 | 0 | ❌ Operator 未创建 | | Redis | 3 | 0 | ❌ Operator 未创建 |
| Sentinel | 3 | 0 | ❌ Operator 未创建 | | Sentinel | 3 | 0 | ❌ Operator 未创建 |
### Services 状态 ### Services 状态
``` ```
✅ kubernetes (内置) ✅ kubernetes (内置)
✅ user-rpc-svc:9001 ✅ user-rpc-svc:9001
✅ user-db-r:5432 (只读副本) ✅ user-db-r:5432 (只读副本)
✅ user-db-ro:5432 (只读副本) ✅ user-db-ro:5432 (只读副本)
✅ user-db-rw:5432 (读写主副本) ✅ user-db-rw:5432 (读写主副本)
``` ```
### HPA 配置 ### HPA 配置
``` ```
✅ user-rpc-hpa-c (CPU 目标: 80%) - 无法工作(pods 未运行) ✅ user-rpc-hpa-c (CPU 目标: 80%) - 无法工作(pods 未运行)
✅ user-rpc-hpa-m (Memory 目标: 80%) - 无法工作(pods 未运行) ✅ user-rpc-hpa-m (Memory 目标: 80%) - 无法工作(pods 未运行)
``` ```
--- ---
## 📝 关键命令速查表 ## 📝 关键命令速查表
```bash ```bash
# 查看 Deployment 状态 # 查看 Deployment 状态
kubectl describe deployment user-rpc kubectl describe deployment user-rpc
# 查看 ReplicaSet 错误事件 # 查看 ReplicaSet 错误事件
kubectl describe replicaset user-rpc-6bf77fbcd9 kubectl describe replicaset user-rpc-6bf77fbcd9
# 查看 Pod 详细错误 # 查看 Pod 详细错误
kubectl describe pod user-rpc-6bf77fbcd9-njm2z kubectl describe pod user-rpc-6bf77fbcd9-njm2z
# 查看 Pod 日志 # 查看 Pod 日志
kubectl logs user-rpc-6bf77fbcd9-njm2z kubectl logs user-rpc-6bf77fbcd9-njm2z
# 查看所有事件(按时间排序) # 查看所有事件(按时间排序)
kubectl get events --sort-by='.lastTimestamp' kubectl get events --sort-by='.lastTimestamp'
# 查看特定命名空间的所有资源 # 查看特定命名空间的所有资源
kubectl get all -n default kubectl get all -n default
# 重新启动 deployment(强制重新创建 pods # 重新启动 deployment(强制重新创建 pods
kubectl rollout restart deployment user-rpc kubectl rollout restart deployment user-rpc
# 查看 Operator 日志 # 查看 Operator 日志
kubectl logs -l app.kubernetes.io/name=redis-operator kubectl logs -l app.kubernetes.io/name=redis-operator
# 检查 CRD 注册状态 # 检查 CRD 注册状态
kubectl api-resources | grep redis kubectl api-resources | grep redis
``` ```
--- ---
## 🎯 总结 ## 🎯 总结
| 问题 | 原因 | 解决状态 | | 问题 | 原因 | 解决状态 |
|------|------|---------| |------|------|---------|
| **ServiceAccount 缺失** | manifest 中声明但未创建 | ✅ **已解决** | | **ServiceAccount 缺失** | manifest 中声明但未创建 | ✅ **已解决** |
| **镜像拉取失败** | 仓库地址不可达或协议不匹配 | ⏳ **待处理** | | **镜像拉取失败** | 仓库地址不可达或协议不匹配 | ⏳ **待处理** |
| **Redis 未部署** | Operator 未响应 CRD | ⏳ **待诊断** | | **Redis 未部署** | Operator 未响应 CRD | ⏳ **待诊断** |
**建议行动** **建议行动**
1. 确认/修复 user-rpc 镜像地址 1. 确认/修复 user-rpc 镜像地址
2. 诊断 Redis Operator 状态 2. 诊断 Redis Operator 状态
3. 验证所有依赖的 ServiceAccounts 和 Secrets 是否存在 3. 验证所有依赖的 ServiceAccounts 和 Secrets 是否存在
4. 考虑在 test.yaml 中添加完整的资源定义,避免手工创建 4. 考虑在 test.yaml 中添加完整的资源定义,避免手工创建
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1179 -1179
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+424 -424
View File
@@ -1,424 +1,424 @@
# JWT Secret + ETCD Encryption Deployment Guide # JWT Secret + ETCD Encryption Deployment Guide
完整的 JWT 认证系统部署指南,包括密钥管理、RBAC 权限控制和 ETCD 加密。 完整的 JWT 认证系统部署指南,包括密钥管理、RBAC 权限控制和 ETCD 加密。
## 部署顺序 ## 部署顺序
### 第1步:创建 Secret 和 RBAC(必需) ### 第1步:创建 Secret 和 RBAC(必需)
创建 JWT 秘钥和服务账户权限: 创建 JWT 秘钥和服务账户权限:
```bash ```bash
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
``` ```
验证创建成功: 验证创建成功:
```bash ```bash
# 检查 Secret # 检查 Secret
kubectl get secret jwt-secret -n juwan kubectl get secret jwt-secret -n juwan
kubectl get secret jwt-secret -n juwan -o yaml kubectl get secret jwt-secret -n juwan -o yaml
# 检查 ServiceAccounts # 检查 ServiceAccounts
kubectl get sa user-rpc -n juwan kubectl get sa user-rpc -n juwan
kubectl get sa envoy-gateway -n juwan kubectl get sa envoy-gateway -n juwan
# 检查 RBAC 权限 # 检查 RBAC 权限
kubectl get role jwt-secret-reader -n juwan kubectl get role jwt-secret-reader -n juwan
kubectl get rolebinding -n juwan -l app=jwt-secret-reader kubectl get rolebinding -n juwan -l app=jwt-secret-reader
``` ```
### 第2步:更新 user-rpc 部署(依赖第1步) ### 第2步:更新 user-rpc 部署(依赖第1步)
已自动更新 `deploy/k8s/service/user/user-rpc.yaml` 已自动更新 `deploy/k8s/service/user/user-rpc.yaml`
- ✅ 更新 `serviceAccountName``find-endpoints``user-rpc` - ✅ 更新 `serviceAccountName``find-endpoints``user-rpc`
- ✅ 添加环境变量 `JWT_SECRET_KEY` 从 Secret `jwt-secret` 读取 - ✅ 添加环境变量 `JWT_SECRET_KEY` 从 Secret `jwt-secret` 读取
应用更新: 应用更新:
```bash ```bash
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
``` ```
验证部署: 验证部署:
```bash ```bash
# 检查 ServiceAccount 已正确绑定 # 检查 ServiceAccount 已正确绑定
kubectl get deployment user-rpc -n juwan -o yaml | grep -A 5 serviceAccountName kubectl get deployment user-rpc -n juwan -o yaml | grep -A 5 serviceAccountName
# 查看 Pod 是否以 user-rpc ServiceAccount 身份运行 # 查看 Pod 是否以 user-rpc ServiceAccount 身份运行
kubectl get pod -n juwan -l app=user-rpc -o yaml | grep serviceAccount kubectl get pod -n juwan -l app=user-rpc -o yaml | grep serviceAccount
# 验证环境变量已注入 # 验证环境变量已注入
kubectl exec -it POD_NAME -n juwan -- env | grep JWT_SECRET_KEY kubectl exec -it POD_NAME -n juwan -- env | grep JWT_SECRET_KEY
``` ```
### 第3步:更新 Envoy 网关部署(依赖第1步) ### 第3步:更新 Envoy 网关部署(依赖第1步)
已自动更新 `deploy/k8s/envoy/envoy.yaml` 已自动更新 `deploy/k8s/envoy/envoy.yaml`
- ✅ 添加 `serviceAccountName: envoy-gateway` 到 Deployment spec - ✅ 添加 `serviceAccountName: envoy-gateway` 到 Deployment spec
应用更新: 应用更新:
```bash ```bash
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
``` ```
验证部署: 验证部署:
```bash ```bash
# 检查 ServiceAccount 已正确绑定 # 检查 ServiceAccount 已正确绑定
kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName
# 检查 Pod 状态 # 检查 Pod 状态
kubectl get pod -n juwan -l app=envoy-gateway kubectl get pod -n juwan -l app=envoy-gateway
``` ```
### 第4步:启用 ETCD 加密(强烈推荐用于生产环境) ### 第4步:启用 ETCD 加密(强烈推荐用于生产环境)
这是一个集群级别的配置,需要在 Kubernetes 控制平面节点上执行。 这是一个集群级别的配置,需要在 Kubernetes 控制平面节点上执行。
**前提条件:** **前提条件:**
- 具有 Kubernetes 集群管理员权限 - 具有 Kubernetes 集群管理员权限
- 可以访问控制平面节点 - 可以访问控制平面节点
- 备份 ETCD 数据库 - 备份 ETCD 数据库
**步骤:** **步骤:**
1. **生成加密密钥** 1. **生成加密密钥**
```bash ```bash
head -c 32 /dev/urandom | base64 head -c 32 /dev/urandom | base64
``` ```
记录输出的 Base64 密钥。 记录输出的 Base64 密钥。
2. **创建加密配置文件** 2. **创建加密配置文件**
在控制平面节点上,创建 `/etc/kubernetes/encryption-config.yaml` 在控制平面节点上,创建 `/etc/kubernetes/encryption-config.yaml`
```yaml ```yaml
apiVersion: apiserver.config.k8s.io/v1 apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration kind: EncryptionConfiguration
resources: resources:
- resources: - resources:
- secrets - secrets
providers: providers:
- aescbc: - aescbc:
keys: keys:
- name: key1 - name: key1
secret: <BASE64_ENCODED_32_BYTE_KEY> secret: <BASE64_ENCODED_32_BYTE_KEY>
- identity: {} - identity: {}
``` ```
替换 `<BASE64_ENCODED_32_BYTE_KEY>` 为第1步生成的密钥。 替换 `<BASE64_ENCODED_32_BYTE_KEY>` 为第1步生成的密钥。
3. **修改 kube-apiserver 配置** 3. **修改 kube-apiserver 配置**
在控制平面节点上,编辑 `/etc/kubernetes/manifests/kube-apiserver.yaml` 在控制平面节点上,编辑 `/etc/kubernetes/manifests/kube-apiserver.yaml`
**添加参数:** **添加参数:**
```yaml ```yaml
spec: spec:
containers: containers:
- name: kube-apiserver - name: kube-apiserver
command: command:
- kube-apiserver - kube-apiserver
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
``` ```
**添加卷挂载:** **添加卷挂载:**
```yaml ```yaml
spec: spec:
containers: containers:
- name: kube-apiserver - name: kube-apiserver
volumeMounts: volumeMounts:
- name: encryption-config - name: encryption-config
mountPath: /etc/kubernetes mountPath: /etc/kubernetes
readOnly: true readOnly: true
volumes: volumes:
- name: encryption-config - name: encryption-config
hostPath: hostPath:
path: /etc/kubernetes path: /etc/kubernetes
type: DirectoryOrCreate type: DirectoryOrCreate
``` ```
4. **重启 kube-apiserver** 4. **重启 kube-apiserver**
修改清单后,kubelet 会自动重启 kube-apiserver。检查状态: 修改清单后,kubelet 会自动重启 kube-apiserver。检查状态:
```bash ```bash
# 在控制平面节点 # 在控制平面节点
kubectl get pods -n kube-system | grep kube-apiserver kubectl get pods -n kube-system | grep kube-apiserver
# 监控重启过程 # 监控重启过程
kubectl logs -n kube-system -l component=kube-apiserver -f kubectl logs -n kube-system -l component=kube-apiserver -f
``` ```
5. **验证加密是否启用** 5. **验证加密是否启用**
创建一个新 Secret 并检查它在 ETCD 中是否加密: 创建一个新 Secret 并检查它在 ETCD 中是否加密:
```bash ```bash
# 创建测试 Secret # 创建测试 Secret
kubectl create secret generic test-secret -n juwan --from-literal=key=value kubectl create secret generic test-secret -n juwan --from-literal=key=value
# 从 control plane 节点检查 ETCD 数据 # 从 control plane 节点检查 ETCD 数据
# 如果数据不可读并包含加密标记,说明加密已启用 # 如果数据不可读并包含加密标记,说明加密已启用
``` ```
6. **保存加密密钥** 6. **保存加密密钥**
⚠️ **关键:将加密密钥安全地保存在离线存储中** ⚠️ **关键:将加密密钥安全地保存在离线存储中**
- 密钥丢失后,无法解密 ETCD 中的数据 - 密钥丢失后,无法解密 ETCD 中的数据
- 无法恢复任何 Secrets - 无法恢复任何 Secrets
- 建议用密码管理工具(如 HashiCorp Vault)或 HSM 存储密钥 - 建议用密码管理工具(如 HashiCorp Vault)或 HSM 存储密钥
### 第5步:验证整个系统 ### 第5步:验证整个系统
完整的验证清单: 完整的验证清单:
```bash ```bash
# 检查所有 Secrets 已创建 # 检查所有 Secrets 已创建
kubectl get secret -n juwan kubectl get secret -n juwan
kubectl get secret jwt-secret -n juwan -o jsonpath='{.data.secret-key}' | base64 -d kubectl get secret jwt-secret -n juwan -o jsonpath='{.data.secret-key}' | base64 -d
# 检查 ServiceAccounts 已创建 # 检查 ServiceAccounts 已创建
kubectl get sa -n juwan kubectl get sa -n juwan
kubectl describe sa user-rpc -n juwan kubectl describe sa user-rpc -n juwan
kubectl describe sa envoy-gateway -n juwan kubectl describe sa envoy-gateway -n juwan
# 检查 RBAC 权限 # 检查 RBAC 权限
kubectl get role -n juwan kubectl get role -n juwan
kubectl get rolebinding -n juwan kubectl get rolebinding -n juwan
kubectl describe role jwt-secret-reader -n juwan kubectl describe role jwt-secret-reader -n juwan
# 测试权限:user-rpc 可以读 jwt-secret # 测试权限:user-rpc 可以读 jwt-secret
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:user-rpc --resource-name=jwt-secret -n juwan kubectl auth can-i get secrets --as=system:serviceaccount:juwan:user-rpc --resource-name=jwt-secret -n juwan
# 测试权限:envoy-gateway 可以读 jwt-secret # 测试权限:envoy-gateway 可以读 jwt-secret
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:envoy-gateway --resource-name=jwt-secret -n juwan kubectl auth can-i get secrets --as=system:serviceaccount:juwan:envoy-gateway --resource-name=jwt-secret -n juwan
# 测试权限:其他 ServiceAccount 无法读取 # 测试权限:其他 ServiceAccount 无法读取
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:other-service -n juwan kubectl auth can-i get secrets --as=system:serviceaccount:juwan:other-service -n juwan
# 检查 Deployments 已正确配置 # 检查 Deployments 已正确配置
kubectl get deployment user-rpc -n juwan -o yaml | grep -A 2 serviceAccountName kubectl get deployment user-rpc -n juwan -o yaml | grep -A 2 serviceAccountName
kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName
# 检查 Pods 是否已启动并运行 # 检查 Pods 是否已启动并运行
kubectl get pods -n juwan -l app=user-rpc kubectl get pods -n juwan -l app=user-rpc
kubectl get pods -n juwan -l app=envoy-gateway kubectl get pods -n juwan -l app=envoy-gateway
# 查看 JWT Secret 是否已挂载到 Pod # 查看 JWT Secret 是否已挂载到 Pod
kubectl exec -it $(kubectl get pod -n juwan -l app=user-rpc -o name | head -1) -n juwan -- env | grep JWT_SECRET_KEY kubectl exec -it $(kubectl get pod -n juwan -l app=user-rpc -o name | head -1) -n juwan -- env | grep JWT_SECRET_KEY
``` ```
## 监控和日志 ## 监控和日志
### 查看 Pod 日志 ### 查看 Pod 日志
```bash ```bash
# user-rpc 日志 # user-rpc 日志
kubectl logs -n juwan -l app=user-rpc -f --all-containers=true kubectl logs -n juwan -l app=user-rpc -f --all-containers=true
# Envoy 日志 # Envoy 日志
kubectl logs -n juwan -l app=envoy-gateway -f kubectl logs -n juwan -l app=envoy-gateway -f
``` ```
### 检查 Pod 事件 ### 检查 Pod 事件
```bash ```bash
# 查看 Pod 创建和启动事件 # 查看 Pod 创建和启动事件
kubectl describe pod -n juwan -l app=user-rpc kubectl describe pod -n juwan -l app=user-rpc
kubectl describe pod -n juwan -l app=envoy-gateway kubectl describe pod -n juwan -l app=envoy-gateway
``` ```
### 权限问题排查 ### 权限问题排查
如果 Pod 无法读取 Secret 如果 Pod 无法读取 Secret
```bash ```bash
# 检查 Pod 使用的 ServiceAccount # 检查 Pod 使用的 ServiceAccount
kubectl get pod POD_NAME -n juwan -o yaml | grep serviceAccountName kubectl get pod POD_NAME -n juwan -o yaml | grep serviceAccountName
# 检查 RBAC 绑定 # 检查 RBAC 绑定
kubectl get rolebinding -n juwan -o wide kubectl get rolebinding -n juwan -o wide
# 检查 Role 权限定义 # 检查 Role 权限定义
kubectl get role jwt-secret-reader -n juwan -o yaml kubectl get role jwt-secret-reader -n juwan -o yaml
# 尝试用 Pod 的身份读取 Secret(需要 kubectl-user-impersonate 或类似工具) # 尝试用 Pod 的身份读取 Secret(需要 kubectl-user-impersonate 或类似工具)
kubectl get secret jwt-secret --as=system:serviceaccount:juwan:user-rpc -n juwan kubectl get secret jwt-secret --as=system:serviceaccount:juwan:user-rpc -n juwan
``` ```
## 安全最佳实践 ## 安全最佳实践
### 1. 密钥轮换 ### 1. 密钥轮换
周期性更换 JWT 秘钥(建议每季度): 周期性更换 JWT 秘钥(建议每季度):
```bash ```bash
# 1. 生成新密钥 # 1. 生成新密钥
NEW_KEY=$(head -c 32 /dev/urandom | base64) NEW_KEY=$(head -c 32 /dev/urandom | base64)
# 2. 更新 Secret # 2. 更新 Secret
kubectl create secret generic jwt-secret \ kubectl create secret generic jwt-secret \
--from-literal=secret-key=$NEW_KEY \ --from-literal=secret-key=$NEW_KEY \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
# 3. 重启 Pods 以加载新密钥(滚动更新) # 3. 重启 Pods 以加载新密钥(滚动更新)
kubectl rollout restart deployment/user-rpc -n juwan kubectl rollout restart deployment/user-rpc -n juwan
kubectl rollout restart deployment/envoy-gateway -n juwan kubectl rollout restart deployment/envoy-gateway -n juwan
# 4. 验证新 Pods 已启动并运行 # 4. 验证新 Pods 已启动并运行
kubectl rollout status deployment/user-rpc -n juwan kubectl rollout status deployment/user-rpc -n juwan
kubectl rollout status deployment/envoy-gateway -n juwan kubectl rollout status deployment/envoy-gateway -n juwan
# 5. 已颁发的旧令牌将变为无效 # 5. 已颁发的旧令牌将变为无效
# 需要用户重新登录获取新令牌 # 需要用户重新登录获取新令牌
``` ```
### 2. 审计和监控 ### 2. 审计和监控
在生产环境中启用 Kubernetes 审计日志来跟踪 Secret 访问: 在生产环境中启用 Kubernetes 审计日志来跟踪 Secret 访问:
```yaml ```yaml
# kube-apiserver 审计策略示例 # kube-apiserver 审计策略示例
apiVersion: audit.k8s.io/v1 apiVersion: audit.k8s.io/v1
kind: Policy kind: Policy
rules: rules:
# 记录 secret 资源的访问 # 记录 secret 资源的访问
- level: RequestResponse - level: RequestResponse
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
resources: ["secrets"] resources: ["secrets"]
# 记录所有认证失败 # 记录所有认证失败
- level: RequestResponse - level: RequestResponse
omitStages: omitStages:
- RequestReceived - RequestReceived
userGroups: ["system:unauthenticated"] userGroups: ["system:unauthenticated"]
- level: Metadata - level: Metadata
omitStages: omitStages:
- RequestReceived - RequestReceived
``` ```
### 3. 网络策略 ### 3. 网络策略
使用 NetworkPolicy 限制 Pods 之间的通信: 使用 NetworkPolicy 限制 Pods 之间的通信:
```yaml ```yaml
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: NetworkPolicy kind: NetworkPolicy
metadata: metadata:
name: jwt-secret-access name: jwt-secret-access
namespace: juwan namespace: juwan
spec: spec:
podSelector: podSelector:
matchLabels: matchLabels:
app: jwt-secret-reader app: jwt-secret-reader
policyTypes: policyTypes:
- Ingress - Ingress
ingress: ingress:
- from: - from:
- podSelector: - podSelector:
matchLabels: matchLabels:
app: user-rpc app: user-rpc
- podSelector: - podSelector:
matchLabels: matchLabels:
app: envoy-gateway app: envoy-gateway
ports: ports:
- protocol: TCP - protocol: TCP
port: 443 # API Server port: 443 # API Server
``` ```
## 灾难恢复 ## 灾难恢复
### 备份 Secret 和 RBAC 配置 ### 备份 Secret 和 RBAC 配置
```bash ```bash
# 备份 JWT Secret # 备份 JWT Secret
kubectl get secret jwt-secret -n juwan -o yaml > jwt-secret-backup.yaml kubectl get secret jwt-secret -n juwan -o yaml > jwt-secret-backup.yaml
# 备份 RBAC 配置 # 备份 RBAC 配置
kubectl get role jwt-secret-reader -n juwan -o yaml > jwt-role-backup.yaml kubectl get role jwt-secret-reader -n juwan -o yaml > jwt-role-backup.yaml
kubectl get rolebinding -n juwan -l app=jwt-secret-reader -o yaml > jwt-rolebinding-backup.yaml kubectl get rolebinding -n juwan -l app=jwt-secret-reader -o yaml > jwt-rolebinding-backup.yaml
# 加密备份文件 # 加密备份文件
gpg --symmetric jwt-secret-backup.yaml gpg --symmetric jwt-secret-backup.yaml
``` ```
### 恢复步骤 ### 恢复步骤
如果 Secret 被意外删除: 如果 Secret 被意外删除:
```bash ```bash
# 从备份恢复 # 从备份恢复
kubectl apply -f jwt-secret-backup.yaml kubectl apply -f jwt-secret-backup.yaml
# 重启 Pods 以重新加载 Secret # 重启 Pods 以重新加载 Secret
kubectl rollout restart deployment/user-rpc -n juwan kubectl rollout restart deployment/user-rpc -n juwan
kubectl rollout restart deployment/envoy-gateway -n juwan kubectl rollout restart deployment/envoy-gateway -n juwan
``` ```
## 常见问题 ## 常见问题
### Q: Pod 无法启动,显示 "failed to pull secret" ### Q: Pod 无法启动,显示 "failed to pull secret"
A: 检查: A: 检查:
1. Secret 是否存在:`kubectl get secret jwt-secret -n juwan` 1. Secret 是否存在:`kubectl get secret jwt-secret -n juwan`
2. ServiceAccount 是否绑定了 RBAC`kubectl describe rolebinding -n juwan` 2. ServiceAccount 是否绑定了 RBAC`kubectl describe rolebinding -n juwan`
3. Secret 名称和命名空间是否正确 3. Secret 名称和命名空间是否正确
### Q: 加密后如何验证 ETCD 中的数据已加密? ### Q: 加密后如何验证 ETCD 中的数据已加密?
A: 从 control plane 节点: A: 从 control plane 节点:
```bash ```bash
# 直接读取 ETCD(如果配置了加密,数据应该不可读) # 直接读取 ETCD(如果配置了加密,数据应该不可读)
sudo strings /var/lib/etcd/member/snap/db | grep -i secret sudo strings /var/lib/etcd/member/snap/db | grep -i secret
``` ```
### Q: 能否更改加密密钥而不重新创建 ETCD? ### Q: 能否更改加密密钥而不重新创建 ETCD?
A: 可以,但流程复杂: A: 可以,但流程复杂:
1. 更新 encryption-config.yaml 中的新密钥 1. 更新 encryption-config.yaml 中的新密钥
2. 将新密钥添加到提供程序列表(保持旧密钥) 2. 将新密钥添加到提供程序列表(保持旧密钥)
3. 重启 kube-apiserver 3. 重启 kube-apiserver
4. 触发重新加密:`kubectl get all --all-namespaces -o json | kubectl apply -f -` 4. 触发重新加密:`kubectl get all --all-namespaces -o json | kubectl apply -f -`
### Q: 如何在 Minikube 中启用 ETCD 加密? ### Q: 如何在 Minikube 中启用 ETCD 加密?
A: 参考 `ENCRYPTION.md` 中的 Minikube 特定说明部分。 A: 参考 `ENCRYPTION.md` 中的 Minikube 特定说明部分。
## 相关文件 ## 相关文件
- `jwt-secret.yaml` - Secret 和 RBAC 配置 - `jwt-secret.yaml` - Secret 和 RBAC 配置
- `ENCRYPTION.md` - ETCD 加密详细文档 - `ENCRYPTION.md` - ETCD 加密详细文档
- `README.md` - 快速参考指南 - `README.md` - 快速参考指南
- `/deploy/k8s/service/user/user-rpc.yaml` - user-rpc Deployment 配置 - `/deploy/k8s/service/user/user-rpc.yaml` - user-rpc Deployment 配置
- `/deploy/k8s/envoy/envoy.yaml` - Envoy 网关 Deployment 配置 - `/deploy/k8s/envoy/envoy.yaml` - Envoy 网关 Deployment 配置
## 下一步 ## 下一步
部署完成后: 部署完成后:
1. **集成 JWT 验证到 RPC Handlers** 1. **集成 JWT 验证到 RPC Handlers**
- 实现 gRPC unary interceptor - 实现 gRPC unary interceptor
- 验证令牌有效性 - 验证令牌有效性
- 处理令牌刷新逻辑 - 处理令牌刷新逻辑
2. **集成 JWT 验证到 Envoy** 2. **集成 JWT 验证到 Envoy**
- 扩展 Lua filter 进行令牌验证 - 扩展 Lua filter 进行令牌验证
- 返回 401(无效令牌)或 200(有效令牌) - 返回 401(无效令牌)或 200(有效令牌)
3. **端到端测试** 3. **端到端测试**
- 创建用户和登录 - 创建用户和登录
- 生成和验证 JWT - 生成和验证 JWT
- 测试令牌刷新和撤销 - 测试令牌刷新和撤销
- 验证 ETCD 加密 - 验证 ETCD 加密
4. **生产部署** 4. **生产部署**
- 启用审计日志 - 启用审计日志
- 配置密钥轮换计划 - 配置密钥轮换计划
- 建立备份和恢复流程 - 建立备份和恢复流程
- 监控 Secret 访问 - 监控 Secret 访问
+129 -129
View File
@@ -1,129 +1,129 @@
# ETCD Encryption Configuration for Kubernetes # ETCD Encryption Configuration for Kubernetes
To enable static encryption at rest for Kubernetes secrets in ETCD, you need to configure the API Server with an EncryptionConfiguration. To enable static encryption at rest for Kubernetes secrets in ETCD, you need to configure the API Server with an EncryptionConfiguration.
## 1. Generate an Encryption Key ## 1. Generate an Encryption Key
```bash ```bash
# Generate a 32-byte base64-encoded key # Generate a 32-byte base64-encoded key
head -c 32 /dev/urandom | base64 head -c 32 /dev/urandom | base64
# Example output: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4= # Example output: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
``` ```
## 2. Create EncryptionConfiguration File ## 2. Create EncryptionConfiguration File
Create `/etc/kubernetes/encryption-config.yaml` on the control plane node: Create `/etc/kubernetes/encryption-config.yaml` on the control plane node:
```yaml ```yaml
apiVersion: apiserver.config.k8s.io/v1 apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration kind: EncryptionConfiguration
resources: resources:
- resources: - resources:
- secrets - secrets
providers: providers:
- aescbc: - aescbc:
keys: keys:
- name: key1 - name: key1
secret: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4= secret: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
- identity: {} - identity: {}
``` ```
## 3. Update kube-apiserver Static Pod Manifest ## 3. Update kube-apiserver Static Pod Manifest
Edit `/etc/kubernetes/manifests/kube-apiserver.yaml` on the control plane node: Edit `/etc/kubernetes/manifests/kube-apiserver.yaml` on the control plane node:
```yaml ```yaml
spec: spec:
containers: containers:
- name: kube-apiserver - name: kube-apiserver
command: command:
- kube-apiserver - kube-apiserver
# ... existing flags ... # ... existing flags ...
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
volumeMounts: volumeMounts:
- name: encryption-config - name: encryption-config
mountPath: /etc/kubernetes mountPath: /etc/kubernetes
readOnly: true readOnly: true
volumes: volumes:
- name: encryption-config - name: encryption-config
hostPath: hostPath:
path: /etc/kubernetes path: /etc/kubernetes
type: DirectoryOrCreate type: DirectoryOrCreate
``` ```
## 4. Verify Encryption is Working ## 4. Verify Encryption is Working
```bash ```bash
# After restarting the API server, create a secret and verify it's encrypted # After restarting the API server, create a secret and verify it's encrypted
kubectl create secret generic test-secret --from-literal=key=value -n juwan kubectl create secret generic test-secret --from-literal=key=value -n juwan
# Check if the secret is encrypted in etcd # Check if the secret is encrypted in etcd
kubectl get secret test-secret -o yaml kubectl get secret test-secret -o yaml
# You can also check raw etcd data (requires etcd access): # You can also check raw etcd data (requires etcd access):
# etcdctl --endpoints=https://127.0.0.1:2379 get /kubernetes.io/secrets/juwan/test-secret # etcdctl --endpoints=https://127.0.0.1:2379 get /kubernetes.io/secrets/juwan/test-secret
# The data should be encrypted (not human-readable) # The data should be encrypted (not human-readable)
``` ```
## 5. Important Notes ## 5. Important Notes
- **Backup your encryption key** in a secure location - **Backup your encryption key** in a secure location
- **Never commit encryption keys** to version control - **Never commit encryption keys** to version control
- If you lose the key, all encrypted secrets will be unrecoverable - If you lose the key, all encrypted secrets will be unrecoverable
- After enabling encryption, existing unencrypted secrets will not be automatically encrypted - After enabling encryption, existing unencrypted secrets will not be automatically encrypted
- To encrypt existing secrets, you can use: `kubectl delete secret <name> && kubectl create secret ...` - To encrypt existing secrets, you can use: `kubectl delete secret <name> && kubectl create secret ...`
- Or use: `kubectl patch secret <name> -p '{}' --type=merge` (triggers re-encryption) - Or use: `kubectl patch secret <name> -p '{}' --type=merge` (triggers re-encryption)
## 6. RBAC Configuration for JWT Secret ## 6. RBAC Configuration for JWT Secret
The `jwt-secret.yaml` includes RBAC rules that: The `jwt-secret.yaml` includes RBAC rules that:
- Create a `jwt-secret` Secret in the `juwan` namespace - Create a `jwt-secret` Secret in the `juwan` namespace
- Create ServiceAccounts for `user-rpc` and `envoy-gateway` - Create ServiceAccounts for `user-rpc` and `envoy-gateway`
- Create a Role `jwt-secret-reader` that allows reading only the `jwt-secret` Secret - Create a Role `jwt-secret-reader` that allows reading only the `jwt-secret` Secret
- Bind this Role to both ServiceAccounts via RoleBindings - Bind this Role to both ServiceAccounts via RoleBindings
This ensures: This ensures:
- Only `user-rpc` and `envoy-gateway` Pods can read the JWT secret - Only `user-rpc` and `envoy-gateway` Pods can read the JWT secret
- Other services and users cannot access the JWT secret - Other services and users cannot access the JWT secret
- Least privilege access principle is enforced - Least privilege access principle is enforced
## 7. Update Deployment to Use ServiceAccount ## 7. Update Deployment to Use ServiceAccount
Make sure your Deployment references the ServiceAccount: Make sure your Deployment references the ServiceAccount:
```yaml ```yaml
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
spec: spec:
template: template:
spec: spec:
serviceAccountName: user-rpc # This is important! serviceAccountName: user-rpc # This is important!
containers: containers:
- name: user-rpc - name: user-rpc
env: env:
- name: JWT_SECRET_KEY - name: JWT_SECRET_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: jwt-secret name: jwt-secret
key: secret-key key: secret-key
``` ```
## 8. For Minikube Users ## 8. For Minikube Users
If using Minikube, you can enable encryption with: If using Minikube, you can enable encryption with:
```bash ```bash
minikube config set apiserver.encryption-provider-config /path/to/encryption-config.yaml minikube config set apiserver.encryption-provider-config /path/to/encryption-config.yaml
minikube start minikube start
``` ```
Or manually edit the kube-apiserver manifest after starting Minikube: Or manually edit the kube-apiserver manifest after starting Minikube:
```bash ```bash
minikube ssh minikube ssh
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
# Add the flags and volume mounts as shown above # Add the flags and volume mounts as shown above
``` ```
+415 -415
View File
@@ -1,415 +1,415 @@
# 部署流程图和时间线 # 部署流程图和时间线
## 部署架构流程图 ## 部署架构流程图
``` ```
┌──────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────┐
│ JWT 认证系统部署流程 │ │ JWT 认证系统部署流程 │
└──────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Phase 1: 前置检查 (5分钟) │ │ Phase 1: 前置检查 (5分钟) │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ ✓ Kubernetes 集群版本 >= 1.24 │ │ ✓ Kubernetes 集群版本 >= 1.24 │
│ ✓ kubectl 已配置,可访问集群 │ │ ✓ kubectl 已配置,可访问集群 │
│ ✓ juwan namespace 已存在 │ │ ✓ juwan namespace 已存在 │
│ ✓ redis-operator CRD 已安装 │ │ ✓ redis-operator CRD 已安装 │
│ ✓ 集群管理员权限(用于 ETCD 加密) │ │ ✓ 集群管理员权限(用于 ETCD 加密) │
│ │ │ │
│ 命令检查: │ │ 命令检查: │
│ $ kubectl cluster-info │ │ $ kubectl cluster-info │
│ $ kubectl get ns juwan │ │ $ kubectl get ns juwan │
│ $ kubectl get crd redisclusters.redis.redis.opstreelabs.in │ │ $ kubectl get crd redisclusters.redis.redis.opstreelabs.in │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Phase 2: 创建 Secret 和 RBAC (5分钟) ⚡ 必需 │ │ Phase 2: 创建 Secret 和 RBAC (5分钟) ⚡ 必需 │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ 执行: │ │ 执行: │
│ $ kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml │ │ $ kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml │
│ │ │ │
│ 创建的资源: │ │ 创建的资源: │
│ ✓ Secret: jwt-secret (包含 JWT 秘钥) │ │ ✓ Secret: jwt-secret (包含 JWT 秘钥) │
│ ✓ ServiceAccount: user-rpc │ │ ✓ ServiceAccount: user-rpc │
│ ✓ ServiceAccount: envoy-gateway │ │ ✓ ServiceAccount: envoy-gateway │
│ ✓ Role: jwt-secret-reader (只读权限) │ │ ✓ Role: jwt-secret-reader (只读权限) │
│ ✓ RoleBinding: jwt-secret-reader-user-rpc │ │ ✓ RoleBinding: jwt-secret-reader-user-rpc │
│ ✓ RoleBinding: jwt-secret-reader-envoy-gateway │ │ ✓ RoleBinding: jwt-secret-reader-envoy-gateway │
│ │ │ │
│ 验证: │ │ 验证: │
│ $ kubectl get secret jwt-secret -n juwan │ │ $ kubectl get secret jwt-secret -n juwan │
│ $ kubectl get sa -n juwan | grep -E "user-rpc|envoy" │ │ $ kubectl get sa -n juwan | grep -E "user-rpc|envoy" │
│ $ kubectl get role -n juwan │ │ $ kubectl get role -n juwan │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Phase 3: 更新 Deployments (10分钟) ⚡ 必需 │ │ Phase 3: 更新 Deployments (10分钟) ⚡ 必需 │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ Step 3a: 更新 user-rpc Deployment │ │ Step 3a: 更新 user-rpc Deployment │
│ 执行: │ │ 执行: │
│ $ kubectl apply -f deploy/k8s/service/user/user-rpc.yaml │ │ $ kubectl apply -f deploy/k8s/service/user/user-rpc.yaml │
│ │ │ │
│ 变更: │ │ 变更: │
│ - serviceAccountName: user-rpc (绑定权限) │ │ - serviceAccountName: user-rpc (绑定权限) │
│ - env.JWT_SECRET_KEY (从 Secret 挂载) │ │ - env.JWT_SECRET_KEY (从 Secret 挂载) │
│ - 保持 Redis Cluster 配置 │ │ - 保持 Redis Cluster 配置 │
│ │ │ │
│ 等待 Pods 启动: │ │ 等待 Pods 启动: │
│ $ kubectl rollout status deployment/user-rpc -n juwan │ │ $ kubectl rollout status deployment/user-rpc -n juwan │
│ │ │ │
│ --- │ │ --- │
│ │ │ │
│ Step 3b: 更新 Envoy Gateway Deployment │ │ Step 3b: 更新 Envoy Gateway Deployment │
│ 执行: │ │ 执行: │
│ $ kubectl apply -f deploy/k8s/envoy/envoy.yaml │ │ $ kubectl apply -f deploy/k8s/envoy/envoy.yaml │
│ │ │ │
│ 变更: │ │ 变更: │
│ - serviceAccountName: envoy-gateway (绑定权限) │ │ - serviceAccountName: envoy-gateway (绑定权限) │
│ - 保持 CSRF Lua 防护配置 │ │ - 保持 CSRF Lua 防护配置 │
│ │ │ │
│ 等待 Pods 启动: │ │ 等待 Pods 启动: │
│ $ kubectl rollout status deployment/envoy-gateway -n juwan │ │ $ kubectl rollout status deployment/envoy-gateway -n juwan │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Phase 4: 验证部署 (15分钟) ⚡ 必需 │ │ Phase 4: 验证部署 (15分钟) ⚡ 必需 │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ 检查清单: │ │ 检查清单: │
│ │ │ │
│ 1️⃣ Secret 和权限验证 │ │ 1️⃣ Secret 和权限验证 │
│ $ kubectl get secret jwt-secret -n juwan │ │ $ kubectl get secret jwt-secret -n juwan │
│ $ kubectl get role jwt-secret-reader -n juwan │ │ $ kubectl get role jwt-secret-reader -n juwan │
│ $ kubectl get rolebinding -n juwan | grep jwt-secret │ │ $ kubectl get rolebinding -n juwan | grep jwt-secret │
│ │ │ │
│ 2️⃣ 权限测试 │ │ 2️⃣ 权限测试 │
│ $ kubectl auth can-i get secrets \ │ │ $ kubectl auth can-i get secrets \ │
│ --as=system:serviceaccount:juwan:user-rpc \ │ │ --as=system:serviceaccount:juwan:user-rpc \ │
│ --resource-name=jwt-secret -n juwan │ │ --resource-name=jwt-secret -n juwan │
│ 预期: yes │ │ 预期: yes │
│ │ │ │
│ 3️⃣ Pods 运行状态 │ │ 3️⃣ Pods 运行状态 │
│ $ kubectl get pods -n juwan -l app=user-rpc │ │ $ kubectl get pods -n juwan -l app=user-rpc │
│ $ kubectl get pods -n juwan -l app=envoy-gateway │ │ $ kubectl get pods -n juwan -l app=envoy-gateway │
│ 预期: 3 个 user-rpc Pods + 1 个 envoy-gateway Pod 都在 Running │ │ 预期: 3 个 user-rpc Pods + 1 个 envoy-gateway Pod 都在 Running │
│ │ │ │
│ 4️⃣ 环境变量验证 │ │ 4️⃣ 环境变量验证 │
│ $ kubectl exec -it <user-rpc-pod> -n juwan -- env | grep JWT │ │ $ kubectl exec -it <user-rpc-pod> -n juwan -- env | grep JWT │
│ 预期: JWT_SECRET_KEY=... │ │ 预期: JWT_SECRET_KEY=... │
│ │ │ │
│ 5️⃣ Redis 连接验证 │ │ 5️⃣ Redis 连接验证 │
│ $ kubectl run redis-cli --image=redis:latest --rm -it \ │ │ $ kubectl run redis-cli --image=redis:latest --rm -it \ │
│ -- redis-cli -h user-redis.juwan:6379 PING │ │ -- redis-cli -h user-redis.juwan:6379 PING │
│ 预期: PONG │ │ 预期: PONG │
│ │ │ │
│ 详见: VERIFICATION.md 第1-8部分 │ │ 详见: VERIFICATION.md 第1-8部分 │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
├─────────── 生产环境额外步骤 ──────────────┐ ├─────────── 生产环境额外步骤 ──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────────────────┐ ┌─────────────────────────────────────┐ ┌──────────────────────────┐ ┌─────────────────────────────────────┐
│ Phase 5a: 应用集成 │ │ Phase 5b: 启用 ETCD 加密 (30分钟) │ │ Phase 5a: 应用集成 │ │ Phase 5b: 启用 ETCD 加密 (30分钟) │
│ (2-3 小时) ⚠️ 推荐 │ │ ⚠️ 生产推荐,需集群管理员权限 │ │ (2-3 小时) ⚠️ 推荐 │ │ ⚠️ 生产推荐,需集群管理员权限 │
├──────────────────────────┤ ├─────────────────────────────────────┤ ├──────────────────────────┤ ├─────────────────────────────────────┤
│ │ │ │ │ │ │ │
│ 实施内容: │ │ 前提条件: │ │ 实施内容: │ │ 前提条件: │
│ ✓ gRPC Interceptor │ │ ✓ Control Plane 节点访问权限 │ │ ✓ gRPC Interceptor │ │ ✓ Control Plane 节点访问权限 │
│ ✓ Login/Logout Handler │ │ ✓ ETCD 备份已创建 │ │ ✓ Login/Logout Handler │ │ ✓ ETCD 备份已创建 │
│ ✓ JWT Middleware │ │ ✓ 加密密钥已生成 │ │ ✓ JWT Middleware │ │ ✓ 加密密钥已生成 │
│ ✓ Token Refresh Logic │ │ │ │ ✓ Token Refresh Logic │ │ │
│ ✓ Error Handling │ │ 步骤: │ │ ✓ Error Handling │ │ 步骤: │
│ ✓ Unit Tests │ │ 1. 生成 32 字节密钥 │ │ ✓ Unit Tests │ │ 1. 生成 32 字节密钥 │
│ │ │ $ head -c 32 /dev/urandom | base64 │ │ │ $ head -c 32 /dev/urandom | base64
│ 参考: │ │ │ │ 参考: │ │ │
│ INTEGRATION.md │ │ 2. 创建加密配置文件 │ │ INTEGRATION.md │ │ 2. 创建加密配置文件 │
│ │ │ /etc/kubernetes/encryption-config.yaml │ │ │ /etc/kubernetes/encryption-config.yaml
│ 时间估计: │ │ │ │ 时间估计: │ │ │
│ - gRPC 拦截器: 30分钟 │ │ 3. 修改 kube-apiserver manifest │ │ - gRPC 拦截器: 30分钟 │ │ 3. 修改 kube-apiserver manifest │
│ - Handlers: 60分钟 │ │ 添加密钥路径和卷挂载 │ │ - Handlers: 60分钟 │ │ 添加密钥路径和卷挂载 │
│ - Middleware: 30分钟 │ │ │ │ - Middleware: 30分钟 │ │ │
│ - 测试: 60分钟 │ │ 4. 重启 kube-apiserver │ │ - 测试: 60分钟 │ │ 4. 重启 kube-apiserver │
│ │ │ kubelet 自动重启 │ │ │ │ kubelet 自动重启 │
│ │ │ │ │ │ │ │
│ │ │ 5. 验证加密已启用 │ │ │ │ 5. 验证加密已启用 │
│ │ │ kubectl create secret generic ... │ │ │ │ kubectl create secret generic ... │
│ │ │ 检查 ETCD 中的数据是否加密 │ │ │ │ 检查 ETCD 中的数据是否加密 │
│ │ │ │ │ │ │ │
│ │ │ 详见: ENCRYPTION.md (8个部分) │ │ │ │ 详见: ENCRYPTION.md (8个部分) │
│ │ │ 验证: VERIFICATION.md 第9部分 │ │ │ │ 验证: VERIFICATION.md 第9部分 │
│ │ │ │ │ │ │ │
└──────────────────────────┘ └─────────────────────────────────────┘ └──────────────────────────┘ └─────────────────────────────────────┘
│ │ │ │
└───────────────────┬─────────────────────┘ └───────────────────┬─────────────────────┘
┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐
│ Phase 6: 完成 ✅ │ │ Phase 6: 完成 ✅ │
├──────────────────────────────────────────┤ ├──────────────────────────────────────────┤
│ │ │ │
│ 最终检查: │ │ 最终检查: │
│ ✓ 所有 Pods 运行正常 │ │ ✓ 所有 Pods 运行正常 │
│ ✓ RBAC 权限已验证 │ │ ✓ RBAC 权限已验证 │
│ ✓ JWT 功能已集成 │ │ ✓ JWT 功能已集成 │
│ ✓ 日志和监控已配置 │ │ ✓ 日志和监控已配置 │
│ ✓ (可选) ETCD 加密已启用 │ │ ✓ (可选) ETCD 加密已启用 │
│ │ │ │
│ 生产推荐: │ │ 生产推荐: │
│ ✓ 启用审计日志 │ │ ✓ 启用审计日志 │
│ ✓ 配置密钥轮换计划(季度) │ │ ✓ 配置密钥轮换计划(季度) │
│ ✓ 备份密钥到安全位置 │ │ ✓ 备份密钥到安全位置 │
│ ✓ 配置告警和监控 │ │ ✓ 配置告警和监控 │
│ │ │ │
└──────────────────────────────────────────┘ └──────────────────────────────────────────┘
``` ```
## 时间估计和路径 ## 时间估计和路径
``` ```
推荐部署路径 推荐部署路径
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
🚀 最快路径 (30 分钟) - 开发环境 🚀 最快路径 (30 分钟) - 开发环境
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
Phase 1: 前置检查 ⏱️ 5 分钟 Phase 1: 前置检查 ⏱️ 5 分钟
Phase 2: 创建 Secret 和 RBAC ⏱️ 5 分钟 Phase 2: 创建 Secret 和 RBAC ⏱️ 5 分钟
Phase 3: 更新 Deployments ⏱️ 10 分钟 Phase 3: 更新 Deployments ⏱️ 10 分钟
Phase 4: 验证部署 ⏱️ 10 分钟 Phase 4: 验证部署 ⏱️ 10 分钟
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
总计: 30 分钟 总计: 30 分钟
📊 默认路径 (75 分钟) - 测试环境 📊 默认路径 (75 分钟) - 测试环境
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
Phase 1-4: 如上 ⏱️ 30 分钟 Phase 1-4: 如上 ⏱️ 30 分钟
Phase 5a: 应用集成(简单版) ⏱️ 45 分钟 Phase 5a: 应用集成(简单版) ⏱️ 45 分钟
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
总计: 75 分钟 总计: 75 分钟
🏆 完整路径 (3.5-4 小时) - 生产环境 🏆 完整路径 (3.5-4 小时) - 生产环境
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
Phase 1-4: 如上 ⏱️ 30 分钟 Phase 1-4: 如上 ⏱️ 30 分钟
Phase 5a: 应用集成(完整版) ⏱️ 2-3 小时 Phase 5a: 应用集成(完整版) ⏱️ 2-3 小时
Phase 5b: ETCD 加密配置 ⏱️ 30 分钟 Phase 5b: ETCD 加密配置 ⏱️ 30 分钟
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
总计: 3.5-4 小时 总计: 3.5-4 小时
📚 学习路径 (6-8 小时) - 从零开始理解 📚 学习路径 (6-8 小时) - 从零开始理解
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
文档阅读 (SUMMARY + DEPLOYMENT + INTEGRATION) ⏱️ 1-2 小时 文档阅读 (SUMMARY + DEPLOYMENT + INTEGRATION) ⏱️ 1-2 小时
完整路径部署 ⏱️ 3.5-4 小时 完整路径部署 ⏱️ 3.5-4 小时
验证和测试 ⏱️ 1-2 小时 验证和测试 ⏱️ 1-2 小时
──────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────
总计: 6-8 小时 总计: 6-8 小时
``` ```
## 并行和串行步骤 ## 并行和串行步骤
``` ```
可以并行执行的任务 可以并行执行的任务
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ 应用集成 (Phase 5a) │ ────┐ │ 应用集成 (Phase 5a) │ ────┐
├─────────────────────────────────┤ │ 可选,独立 ├─────────────────────────────────┤ │ 可选,独立
│ • gRPC interceptor │ │ 进行 │ • gRPC interceptor │ │ 进行
│ • REST middleware │ │ │ • REST middleware │ │
│ • Handler 实现 │ │ │ • Handler 实现 │ │
│ • 单元测试 │ │ │ • 单元测试 │ │
└─────────────────────────────────┘ │ └─────────────────────────────────┘ │
├─ 与 Phase 2-4 并行 ├─ 与 Phase 2-4 并行
┌─────────────────────────────────┐ │ ┌─────────────────────────────────┐ │
│ ETCD 加密 (Phase 5b) │ ────┘ │ ETCD 加密 (Phase 5b) │ ────┘
├─────────────────────────────────┤ │ 需要集群管理员 ├─────────────────────────────────┤ │ 需要集群管理员
│ • 生成密钥(可单独进行) │ │ 权限,在 │ • 生成密钥(可单独进行) │ │ 权限,在
│ • 创建配置文件 │ │ Control Plane │ • 创建配置文件 │ │ Control Plane
│ • 修改 kube-apiserver │ │ 节点执行 │ • 修改 kube-apiserver │ │ 节点执行
│ • 重启 API server │ │ │ • 重启 API server │ │
└─────────────────────────────────┘────┘ └─────────────────────────────────┘────┘
必须串行执行的步骤 必须串行执行的步骤
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
Phase 1 → Phase 2 → Phase 3 → Phase 4 → (Phase 5a + Phase 5b) Phase 1 → Phase 2 → Phase 3 → Phase 4 → (Phase 5a + Phase 5b)
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
前置检查 创建资源 部署应用 验证完整 可选扩展功能 前置检查 创建资源 部署应用 验证完整 可选扩展功能
• Phase 2 必须在 Phase 1 之后(需要 namespace • Phase 2 必须在 Phase 1 之后(需要 namespace
• Phase 3 必须在 Phase 2 之后(需要 RoleBinding • Phase 3 必须在 Phase 2 之后(需要 RoleBinding
• Phase 4 必须在 Phase 3 之后(需要 Pods 启动) • Phase 4 必须在 Phase 3 之后(需要 Pods 启动)
• Phase 5a/5b 可在 Phase 4 完成后并行进行 • Phase 5a/5b 可在 Phase 4 完成后并行进行
``` ```
## 关键时间点 ## 关键时间点
``` ```
事件时间线 事件时间线
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
T+0 Phase 1: 验证前置条件 T+0 Phase 1: 验证前置条件
└─ 预计 5 分钟 └─ 预计 5 分钟
T+5 Phase 2: kubectl apply jwt-secret.yaml T+5 Phase 2: kubectl apply jwt-secret.yaml
└─ 预计 1 分钟执行,5 分钟验证 └─ 预计 1 分钟执行,5 分钟验证
T+11 Phase 3a: kubectl apply user-rpc.yaml T+11 Phase 3a: kubectl apply user-rpc.yaml
└─ 3 个 Pods 启动(滚动更新) └─ 3 个 Pods 启动(滚动更新)
└─ 预计 ~3 分钟(取决于镜像拉取) └─ 预计 ~3 分钟(取决于镜像拉取)
T+14 Phase 3b: kubectl apply envoy.yaml T+14 Phase 3b: kubectl apply envoy.yaml
└─ 1 个 Pod 启动 └─ 1 个 Pod 启动
└─ 预计 ~2 分钟 └─ 预计 ~2 分钟
T+16 Phase 4: 执行完整验证检查 T+16 Phase 4: 执行完整验证检查
└─ 12 个验证部分,共 ~15 分钟 └─ 12 个验证部分,共 ~15 分钟
T+31 ✅ 基础部署完成 T+31 ✅ 基础部署完成
T+31 (可选) Phase 5a: 应用代码集成 T+31 (可选) Phase 5a: 应用代码集成
到 └─ 2-3 小时编码和测试 到 └─ 2-3 小时编码和测试
T+211 T+211
T+31 (可选) Phase 5b: ETCD 加密 T+31 (可选) Phase 5b: ETCD 加密
到 └─ 30 分钟配置 到 └─ 30 分钟配置
T+61 T+61
T+211 或 T+61 🎉 全部完成 T+211 或 T+61 🎉 全部完成
(取决于是否执行 Phase 5) (取决于是否执行 Phase 5)
``` ```
## 推荐的部署顺序 ## 推荐的部署顺序
### 对于 DevOps/SRE ### 对于 DevOps/SRE
``` ```
优先级顺序: 优先级顺序:
1️⃣ Phase 1-4 (核心部署) [必需] 1️⃣ Phase 1-4 (核心部署) [必需]
└─ 时间: 30 分钟 └─ 时间: 30 分钟
2️⃣ Phase 5b (ETCD 加密) [生产强烈推荐] 2️⃣ Phase 5b (ETCD 加密) [生产强烈推荐]
└─ 时间: 30 分钟 └─ 时间: 30 分钟
└─ 开始时间: T+16 之前 (与 Phase 4 并行) └─ 开始时间: T+16 之前 (与 Phase 4 并行)
3️⃣ 密钥备份和恢复计划 [重要] 3️⃣ 密钥备份和恢复计划 [重要]
└─ 时间: 15 分钟 └─ 时间: 15 分钟
└─ 参考: DEPLOYMENT.md 灾难恢复 └─ 参考: DEPLOYMENT.md 灾难恢复
4️⃣ Phase 5a 支持 [当开发完成时] 4️⃣ Phase 5a 支持 [当开发完成时]
└─ 协助开发团队集成 JWT └─ 协助开发团队集成 JWT
└─ 参考: INTEGRATION.md └─ 参考: INTEGRATION.md
``` ```
### 对于应用开发者 ### 对于应用开发者
``` ```
优先级顺序: 优先级顺序:
1️⃣ 了解系统架构 [了解背景] 1️⃣ 了解系统架构 [了解背景]
└─ 文档: SUMMARY.md └─ 文档: SUMMARY.md
└─ 时间: 10 分钟 └─ 时间: 10 分钟
2️⃣ Phase 5a: 代码集成 [并行进行] 2️⃣ Phase 5a: 代码集成 [并行进行]
└─ 参考: INTEGRATION.md └─ 参考: INTEGRATION.md
└─ 时间: 2-3 小时 └─ 时间: 2-3 小时
3️⃣ 单元测试 [在开发中] 3️⃣ 单元测试 [在开发中]
└─ 参考: INTEGRATION.md 第 9 部分 └─ 参考: INTEGRATION.md 第 9 部分
└─ 时间: 1 小时 └─ 时间: 1 小时
4️⃣ 集成测试 [与运维协调] 4️⃣ 集成测试 [与运维协调]
└─ 测试完整流程 └─ 测试完整流程
└─ 时间: 1-2 小时 └─ 时间: 1-2 小时
``` ```
## 回滚应急步骤 ## 回滚应急步骤
如果部署失败,可以快速回滚: 如果部署失败,可以快速回滚:
``` ```
紧急回滚 紧急回滚
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
如果 Phase 2 (Secret) 失败: 如果 Phase 2 (Secret) 失败:
→ kubectl delete secret jwt-secret -n juwan → kubectl delete secret jwt-secret -n juwan
→ kubectl delete sa user-rpc envoy-gateway -n juwan → kubectl delete sa user-rpc envoy-gateway -n juwan
→ kubectl delete role jwt-secret-reader -n juwan → kubectl delete role jwt-secret-reader -n juwan
→ 修正配置后重新应用 → 修正配置后重新应用
如果 Phase 3 (Deployment) 失败: 如果 Phase 3 (Deployment) 失败:
→ kubectl rollout undo deployment/user-rpc -n juwan → kubectl rollout undo deployment/user-rpc -n juwan
→ kubectl rollout undo deployment/envoy-gateway -n juwan → kubectl rollout undo deployment/envoy-gateway -n juwan
→ 或删除部署并使用稳定的旧版本重新部署 → 或删除部署并使用稳定的旧版本重新部署
如果 Phase 5b (ETCD 加密) 失败: 如果 Phase 5b (ETCD 加密) 失败:
→ 从 kube-apiserver 清单中移除加密参数 → 从 kube-apiserver 清单中移除加密参数
→ 重启 kube-apiserver → 重启 kube-apiserver
→ 删除 /etc/kubernetes/encryption-config.yaml → 删除 /etc/kubernetes/encryption-config.yaml
→ 从最近的 ETCD 备份恢复(如果需要) → 从最近的 ETCD 备份恢复(如果需要)
注意: 备份是关键!每次重大操作前都应备份。 注意: 备份是关键!每次重大操作前都应备份。
``` ```
## 部署检查点 (Go/No-Go) ## 部署检查点 (Go/No-Go)
``` ```
关键检查点 关键检查点
═════════════════════════════════════════════════════════════════ ═════════════════════════════════════════════════════════════════
✅ Checkpoint 1 (Phase 2 后) ✅ Checkpoint 1 (Phase 2 后)
- Secret 已创建 - Secret 已创建
- ServiceAccounts 已创建 - ServiceAccounts 已创建
- Go → 继续 Phase 3 - Go → 继续 Phase 3
- No-Go → 检查 kubectl 权限 - No-Go → 检查 kubectl 权限
✅ Checkpoint 2 (Phase 3 后) ✅ Checkpoint 2 (Phase 3 后)
- Pods 已启动 (Running) - Pods 已启动 (Running)
- No PodSchedulingFailure - No PodSchedulingFailure
- Go → 继续 Phase 4 - Go → 继续 Phase 4
- No-Go → 检查资源限制和镜像拉取 - No-Go → 检查资源限制和镜像拉取
✅ Checkpoint 3 (Phase 4 权限测试) ✅ Checkpoint 3 (Phase 4 权限测试)
- user-rpc 可以读 jwt-secret - user-rpc 可以读 jwt-secret
- envoy-gateway 可以读 jwt-secret - envoy-gateway 可以读 jwt-secret
- 其他 SA 无法读取 - 其他 SA 无法读取
- Go → 继续 Phase 5 - Go → 继续 Phase 5
- No-Go → 检查 RBAC 配置 - No-Go → 检查 RBAC 配置
✅ Checkpoint 4 (Phase 4 Redis 连接) ✅ Checkpoint 4 (Phase 4 Redis 连接)
- Redis Cluster 健康 (3/3 nodes) - Redis Cluster 健康 (3/3 nodes)
- PING 返回 PONG - PING 返回 PONG
- Go → 继续应用集成 - Go → 继续应用集成
- No-Go → 检查 Redis Pods 和网络 - No-Go → 检查 Redis Pods 和网络
✅ Checkpoint 5 (Phase 5b - ETCD 加密) ✅ Checkpoint 5 (Phase 5b - ETCD 加密)
- 新创建的 Secret 在 ETCD 中已加密 - 新创建的 Secret 在 ETCD 中已加密
- Go → 生产就绪 - Go → 生产就绪
- No-Go → 检查 kube-apiserver 配置参数 - No-Go → 检查 kube-apiserver 配置参数
``` ```
--- ---
## 使用这个流程图 ## 使用这个流程图
1. **首次部署** → 从 "推荐部署路径" 选择合适的版本 1. **首次部署** → 从 "推荐部署路径" 选择合适的版本
2. **卡在某一步** → 查看对应的 Phase 描述和命令 2. **卡在某一步** → 查看对应的 Phase 描述和命令
3. **估算时间** → 查看 "时间估计和路径" 部分 3. **估算时间** → 查看 "时间估计和路径" 部分
4. **需要回滚** → 参考 "回滚应急步骤" 4. **需要回滚** → 参考 "回滚应急步骤"
5. **检查进度** → 使用 "部署检查点" 5. **检查进度** → 使用 "部署检查点"
详细的部署步骤见:[DEPLOYMENT.md](./DEPLOYMENT.md) 详细的部署步骤见:[DEPLOYMENT.md](./DEPLOYMENT.md)
+399 -399
View File
@@ -1,399 +1,399 @@
# JWT + ETCD 加密系统文档索引 # JWT + ETCD 加密系统文档索引
## 📚 文档完整导航 ## 📚 文档完整导航
### 快速入门 (5-15分钟) ### 快速入门 (5-15分钟)
**推荐路径:** 从上到下顺序阅读 **推荐路径:** 从上到下顺序阅读
1. **[SUMMARY.md](./SUMMARY.md)** ⭐ 从这里开始 1. **[SUMMARY.md](./SUMMARY.md)** ⭐ 从这里开始
- 📋 项目概览和架构图 - 📋 项目概览和架构图
- 🎯 核心特性一览 - 🎯 核心特性一览
- ✅ 生产就绪检查清单 - ✅ 生产就绪检查清单
- 🚀 下一步行动计划 - 🚀 下一步行动计划
2. **[README.md](./README.md)** 2. **[README.md](./README.md)**
- 🏃 4个快速部署步骤 - 🏃 4个快速部署步骤
- 🔐 安全考虑事项 - 🔐 安全考虑事项
- 🔄 密钥轮换程序 - 🔄 密钥轮换程序
- 🆘 故障排查 - 🆘 故障排查
3. **[QUICK_REFERENCE.md](./QUICK_REFERENCE.md)** 3. **[QUICK_REFERENCE.md](./QUICK_REFERENCE.md)**
- ⚡ 一页速查表 - ⚡ 一页速查表
- 📝 常见命令复制粘贴 - 📝 常见命令复制粘贴
- 🗺️ 文档地图 - 🗺️ 文档地图
- 🎓 关键参数速知 - 🎓 关键参数速知
### 部署实施 (30-60分钟) ### 部署实施 (30-60分钟)
4. **[DEPLOYMENT.md](./DEPLOYMENT.md)** - 最详细的部署指南 4. **[DEPLOYMENT.md](./DEPLOYMENT.md)** - 最详细的部署指南
- 📦 第1步:创建 Secret 和 RBAC(必需) - 📦 第1步:创建 Secret 和 RBAC(必需)
- 🔄 第2步:更新 user-rpc Deployment - 🔄 第2步:更新 user-rpc Deployment
- 🌐 第3步:更新 Envoy Gateway Deployment - 🌐 第3步:更新 Envoy Gateway Deployment
- 🔐 第4步:启用 ETCD 加密(生产推荐) - 🔐 第4步:启用 ETCD 加密(生产推荐)
- ✔️ 第5步:验证整个系统 - ✔️ 第5步:验证整个系统
- 📊 监控和日志配置 - 📊 监控和日志配置
- 🛠️ 安全最佳实践 - 🛠️ 安全最佳实践
- 🆘 故障排查指南 - 🆘 故障排查指南
- 💾 灾难恢复流程 - 💾 灾难恢复流程
### 验证和监控 (20-30分钟) ### 验证和监控 (20-30分钟)
5. **[VERIFICATION.md](./VERIFICATION.md)** - 完整验证清单 5. **[VERIFICATION.md](./VERIFICATION.md)** - 完整验证清单
**12个验证部分:** **12个验证部分:**
| 部分 | 用途 | 时间 | | 部分 | 用途 | 时间 |
|-----|------|------| |-----|------|------|
| 第1部分 | Secret/RBAC 基础验证 | 2分钟 | | 第1部分 | Secret/RBAC 基础验证 | 2分钟 |
| 第2部分 | 权限测试(allow/deny | 3分钟 | | 第2部分 | 权限测试(allow/deny | 3分钟 |
| 第3部分 | Deployment 配置检查 | 2分钟 | | 第3部分 | Deployment 配置检查 | 2分钟 |
| 第4部分 | Redis 连接测试 | 2分钟 | | 第4部分 | Redis 连接测试 | 2分钟 |
| 第5部分 | 应用启动日志 | 3分钟 | | 第5部分 | 应用启动日志 | 3分钟 |
| 第6部分 | 网络和服务发现 | 3分钟 | | 第6部分 | 网络和服务发现 | 3分钟 |
| 第7部分 | Prometheus 指标 | 3分钟 | | 第7部分 | Prometheus 指标 | 3分钟 |
| 第8部分 | Loki 日志聚合 | 2分钟 | | 第8部分 | Loki 日志聚合 | 2分钟 |
| 第9部分 | ETCD 加密验证 | 5分钟 | | 第9部分 | ETCD 加密验证 | 5分钟 |
| 第10部分 | JWT 功能测试 | 10分钟 | | 第10部分 | JWT 功能测试 | 10分钟 |
| 第11部分 | 故障排查诊断 | 5分钟 | | 第11部分 | 故障排查诊断 | 5分钟 |
| 第12部分 | 清理和总结 | 2分钟 | | 第12部分 | 清理和总结 | 2分钟 |
### 高级话题 ### 高级话题
6. **[ENCRYPTION.md](./ENCRYPTION.md)** - ETCD 加密完整指南 6. **[ENCRYPTION.md](./ENCRYPTION.md)** - ETCD 加密完整指南
- 🔑 第1部分:密钥生成 - 🔑 第1部分:密钥生成
- 📋 第2部分:配置格式 - 📋 第2部分:配置格式
- 🔧 第3部分:kube-apiserver 修改 - 🔧 第3部分:kube-apiserver 修改
- ✅第4部分:验证加密 - ✅第4部分:验证加密
- ⚠️ 第5部分:关键警告(数据不可恢复) - ⚠️ 第5部分:关键警告(数据不可恢复)
- 🔐 第6部分:RBAC 解释 - 🔐 第6部分:RBAC 解释
- 📦 第7部分:Deployment 示例 - 📦 第7部分:Deployment 示例
- 🍎 第8部分:Minikube 特定说明 - 🍎 第8部分:Minikube 特定说明
7. **[INTEGRATION.md](../api/INTEGRATION.md)** - 代码集成指南 7. **[INTEGRATION.md](../api/INTEGRATION.md)** - 代码集成指南
- 🔗 第1部分:gRPC Unary Interceptor - 🔗 第1部分:gRPC Unary Interceptor
- 🔗 第2部分:gRPC Stream Interceptor - 🔗 第2部分:gRPC Stream Interceptor
- 👤 第3部分:登录 Handler 实现 - 👤 第3部分:登录 Handler 实现
- 🔐 第4部分:受保护 Handler 中的声明提取 - 🔐 第4部分:受保护 Handler 中的声明提取
- 🔄 第5部分:令牌刷新端点 - 🔄 第5部分:令牌刷新端点
- 🚪 第6部分:登出处理 - 🚪 第6部分:登出处理
- 🛣️ 第7部分:REST Routes 配置 - 🛣️ 第7部分:REST Routes 配置
- 🔎 第8部分:错误处理最佳实践 - 🔎 第8部分:错误处理最佳实践
- 🧪 第9部分:单元测试示例 - 🧪 第9部分:单元测试示例
--- ---
## 📁 文件结构详解 ## 📁 文件结构详解
``` ```
deploy/k8s/ deploy/k8s/
├── secrets/ ├── secrets/
│ ├── jwt-secret.yaml ✅ Kubernetes 清单文件 │ ├── jwt-secret.yaml ✅ Kubernetes 清单文件
│ │ ├── Secret: jwt-secret (JWT 秘钥数据) │ │ ├── Secret: jwt-secret (JWT 秘钥数据)
│ │ ├── ServiceAccount: user-rpc │ │ ├── ServiceAccount: user-rpc
│ │ ├── ServiceAccount: envoy-gateway │ │ ├── ServiceAccount: envoy-gateway
│ │ ├── Role: jwt-secret-reader │ │ ├── Role: jwt-secret-reader
│ │ ├── RoleBinding: jwt-secret-reader-user-rpc │ │ ├── RoleBinding: jwt-secret-reader-user-rpc
│ │ └── RoleBinding: jwt-secret-reader-envoy-gateway │ │ └── RoleBinding: jwt-secret-reader-envoy-gateway
│ │ │ │
│ ├── README.md 📖 快速参考指南(5分钟入门) │ ├── README.md 📖 快速参考指南(5分钟入门)
│ ├── SUMMARY.md 📊 系统概览(10分钟了解全貌) │ ├── SUMMARY.md 📊 系统概览(10分钟了解全貌)
│ ├── QUICK_REFERENCE.md ⚡ 速查表(查找命令和参数) │ ├── QUICK_REFERENCE.md ⚡ 速查表(查找命令和参数)
│ ├── DEPLOYMENT.md 📦 详细部署指南(60分钟完整部署) │ ├── DEPLOYMENT.md 📦 详细部署指南(60分钟完整部署)
│ ├── ENCRYPTION.md 🔐 ETCD 加密指南(Control Plane 配置) │ ├── ENCRYPTION.md 🔐 ETCD 加密指南(Control Plane 配置)
│ ├── VERIFICATION.md ✅ 验证清单(部署后验证) │ ├── VERIFICATION.md ✅ 验证清单(部署后验证)
│ └── INDEX.md 🗺️ 本文件(文档导航) │ └── INDEX.md 🗺️ 本文件(文档导航)
└── envoy/ └── envoy/
│ └── envoy.yaml ✅ Envoy 网关配置 │ └── envoy.yaml ✅ Envoy 网关配置
│ └── 已更新: serviceAccountName: envoy-gateway │ └── 已更新: serviceAccountName: envoy-gateway
service/user/ service/user/
├── user-api.yaml ✅ user-api Service ├── user-api.yaml ✅ user-api Service
├── user-rpc.yaml ✅ user-rpc Deployment(已更新) ├── user-rpc.yaml ✅ user-rpc Deployment(已更新)
│ ├── serviceAccountName: user-rpc (已更新) │ ├── serviceAccountName: user-rpc (已更新)
│ ├── JWT_SECRET_KEY env var (已更新) │ ├── JWT_SECRET_KEY env var (已更新)
│ └── Redis Cluster configuration │ └── Redis Cluster configuration
└── ... └── ...
app/users/ app/users/
├── api/ ├── api/
│ └── INTEGRATION.md 📝 REST/gRPC 集成指南 │ └── INTEGRATION.md 📝 REST/gRPC 集成指南
└── rpc/ └── rpc/
├── internal/utils/jwt.go ✅ JwtManager 实现(已存在) ├── internal/utils/jwt.go ✅ JwtManager 实现(已存在)
├── internal/config/config.go ✅ JWT 配置(已存在) ├── internal/config/config.go ✅ JWT 配置(已存在)
├── internal/svc/ ├── internal/svc/
│ └── serviceContext.go ✅ 依赖注入(已存在) │ └── serviceContext.go ✅ 依赖注入(已存在)
└── etc/pb.yaml ✅ 运行时配置(已存在) └── etc/pb.yaml ✅ 运行时配置(已存在)
``` ```
--- ---
## 🎯 按场景查找文档 ## 🎯 按场景查找文档
### 场景 1:我想快速了解这个系统是什么 ### 场景 1:我想快速了解这个系统是什么
**推荐阅读顺序:** **推荐阅读顺序:**
1. [SUMMARY.md](./SUMMARY.md) - 项目概览(5分钟) 1. [SUMMARY.md](./SUMMARY.md) - 项目概览(5分钟)
2. [SUMMARY.md](./SUMMARY.md) 中的架构图和特性说明 2. [SUMMARY.md](./SUMMARY.md) 中的架构图和特性说明
**关键信息:** **关键信息:**
- JWT 令牌系统 + Redis 存储 + RBAC 权限 + ETCD 加密 - JWT 令牌系统 + Redis 存储 + RBAC 权限 + ETCD 加密
- 支持 7 天有效期、30 天可刷新 - 支持 7 天有效期、30 天可刷新
- Envoy 网关 CSRF 防护 - Envoy 网关 CSRF 防护
--- ---
### 场景 2:我想立即部署到 Kubernetes ### 场景 2:我想立即部署到 Kubernetes
**推荐阅读顺序:** **推荐阅读顺序:**
1. [README.md](./README.md) - 快速参考(2分钟) 1. [README.md](./README.md) - 快速参考(2分钟)
2. [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - 复制粘贴命令(3分钟) 2. [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - 复制粘贴命令(3分钟)
3. 运行部署命令(5分钟) 3. 运行部署命令(5分钟)
4. [VERIFICATION.md](./VERIFICATION.md) 第1-7部分 - 验证(10分钟) 4. [VERIFICATION.md](./VERIFICATION.md) 第1-7部分 - 验证(10分钟)
**快速命令:** **快速命令:**
```bash ```bash
# Copy from QUICK_REFERENCE.md "部署命令" 部分 # Copy from QUICK_REFERENCE.md "部署命令" 部分
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
``` ```
--- ---
### 场景 3:部署后验证一切正常 ### 场景 3:部署后验证一切正常
**推荐阅读:** **推荐阅读:**
- [VERIFICATION.md](./VERIFICATION.md) - 12部分完整验证清单 - [VERIFICATION.md](./VERIFICATION.md) - 12部分完整验证清单
- 逐部分执行验证命令 - 逐部分执行验证命令
**预计时间:** 30-40分钟 **预计时间:** 30-40分钟
**验证触发点:** **验证触发点:**
- ✅ Secrets 和 RBAC 已创建 - ✅ Secrets 和 RBAC 已创建
- ✅ Pods 已启动运行 - ✅ Pods 已启动运行
- ✅ 权限验证通过 - ✅ 权限验证通过
- ✅ Redis 连接成功 - ✅ Redis 连接成功
--- ---
### 场景 4:启用 ETCD 加密(生产推荐) ### 场景 4:启用 ETCD 加密(生产推荐)
**推荐阅读顺序:** **推荐阅读顺序:**
1. [ENCRYPTION.md](./ENCRYPTION.md) - 完整加密指南 1. [ENCRYPTION.md](./ENCRYPTION.md) - 完整加密指南
2. 按照 8 个步骤逐一执行 2. 按照 8 个步骤逐一执行
3. [VERIFICATION.md](./VERIFICATION.md) 第9部分 - 加密验证 3. [VERIFICATION.md](./VERIFICATION.md) 第9部分 - 加密验证
**需要的权限:** **需要的权限:**
- Control Plane 节点的 root/sudo 权限 - Control Plane 节点的 root/sudo 权限
- Kubernetes 集群管理员权限 - Kubernetes 集群管理员权限
**预计时间:** 15-20分钟 **预计时间:** 15-20分钟
--- ---
### 场景 5:集成 JWT 到我的应用代码中 ### 场景 5:集成 JWT 到我的应用代码中
**推荐阅读顺序:** **推荐阅读顺序:**
1. [INTEGRATION.md](../api/INTEGRATION.md) 第1-2部分 - gRPC 拦截器 1. [INTEGRATION.md](../api/INTEGRATION.md) 第1-2部分 - gRPC 拦截器
2. 第3-4部分 - 登录和受保护 Handlers 2. 第3-4部分 - 登录和受保护 Handlers
3. 第7-8部分 - REST API 中间件 3. 第7-8部分 - REST API 中间件
4. 第9部分 - 单元测试 4. 第9部分 - 单元测试
**需要实现:** **需要实现:**
- ✅ gRPC Unary/Stream Interceptors - ✅ gRPC Unary/Stream Interceptors
- ✅ 登录/登出端点 - ✅ 登录/登出端点
- ✅ JWT Middleware for REST - ✅ JWT Middleware for REST
- ✅ 错误处理 - ✅ 错误处理
**预计时间:** 2-3 小时 **预计时间:** 2-3 小时
--- ---
### 场景 6:部署后遇到问题 ### 场景 6:部署后遇到问题
**根据错误类型选择:** **根据错误类型选择:**
| 错误类型 | 查看文档 | | 错误类型 | 查看文档 |
|---------|--------| |---------|--------|
| Pod 无法启动 | [VERIFICATION.md](./VERIFICATION.md) 第11部分 | | Pod 无法启动 | [VERIFICATION.md](./VERIFICATION.md) 第11部分 |
| 权限被拒绝 | [VERIFICATION.md](./VERIFICATION.md) 第2部分 + [README.md](./README.md) 故障排查 | | 权限被拒绝 | [VERIFICATION.md](./VERIFICATION.md) 第2部分 + [README.md](./README.md) 故障排查 |
| Redis 连接失败 | [VERIFICATION.md](./VERIFICATION.md) 第4部分 | | Redis 连接失败 | [VERIFICATION.md](./VERIFICATION.md) 第4部分 |
| ETCD 加密失败 | [ENCRYPTION.md](./ENCRYPTION.md) 第5-6部分 | | ETCD 加密失败 | [ENCRYPTION.md](./ENCRYPTION.md) 第5-6部分 |
| 配置不清楚 | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 配置文件位置 | | 配置不清楚 | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 配置文件位置 |
--- ---
### 场景 7:定期维护任务 ### 场景 7:定期维护任务
#### 任务:轮换 JWT 秘钥 #### 任务:轮换 JWT 秘钥
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 安全最佳实践 > 密钥轮换 **阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 安全最佳实践 > 密钥轮换
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 密钥轮换步骤 **或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 密钥轮换步骤
**频率:** 季度(3个月) **频率:** 季度(3个月)
#### 任务:轮换 ETCD 加密密钥 #### 任务:轮换 ETCD 加密密钥
**阅读:** [ENCRYPTION.md](./ENCRYPTION.md) 第5部分 **阅读:** [ENCRYPTION.md](./ENCRYPTION.md) 第5部分
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) ETCD 加密系统 **或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) ETCD 加密系统
**频率:** 年度(12个月) **频率:** 年度(12个月)
#### 任务:备份密钥 #### 任务:备份密钥
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 灾难恢复 **阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 灾难恢复
**或:** [ENCRYPTION.md](./ENCRYPTION.md) 关键警告 **或:** [ENCRYPTION.md](./ENCRYPTION.md) 关键警告
**频率:** 立即 + 每次轮换后 **频率:** 立即 + 每次轮换后
--- ---
## 📊 文档深度对比 ## 📊 文档深度对比
| 文档 | 深度 | 丰富度 | 代码 | 适合角色 | | 文档 | 深度 | 丰富度 | 代码 | 适合角色 |
|-----|------|--------|------|---------| |-----|------|--------|------|---------|
| README | 浅 | 概览 | - | PM/初学者 | | README | 浅 | 概览 | - | PM/初学者 |
| SUMMARY | 浅 | 概览 | - | 决策者 | | SUMMARY | 浅 | 概览 | - | 决策者 |
| QUICK_REFERENCE | 中 | 速查 | 命令 | DevOps/SRE | | QUICK_REFERENCE | 中 | 速查 | 命令 | DevOps/SRE |
| DEPLOYMENT | 深 | 详细 | 示例 | DevOps/运维 | | DEPLOYMENT | 深 | 详细 | 示例 | DevOps/运维 |
| VERIFICATION | 深 | 详细 | 脚本 | QA/DevOps | | VERIFICATION | 深 | 详细 | 脚本 | QA/DevOps |
| ENCRYPTION | 非常深 | 极详细 | YAML | 安全/运维 | | ENCRYPTION | 非常深 | 极详细 | YAML | 安全/运维 |
| INTEGRATION | 非常深 | 代码级 | 完整 | 开发者 | | INTEGRATION | 非常深 | 代码级 | 完整 | 开发者 |
--- ---
## 🔄 学习路径建议 ## 🔄 学习路径建议
### 对于 DevOps/SRE ### 对于 DevOps/SRE
1. SUMMARY.md (5 分钟) 1. SUMMARY.md (5 分钟)
2. DEPLOYMENT.md (30 分钟) 2. DEPLOYMENT.md (30 分钟)
3. VERIFICATION.md (30 分钟) 3. VERIFICATION.md (30 分钟)
4. ENCRYPTION.md (20 分钟) 4. ENCRYPTION.md (20 分钟)
5. 实践部署 (60 分钟) 5. 实践部署 (60 分钟)
**总计:** ~3 小时 **总计:** ~3 小时
### 对于应用开发者 ### 对于应用开发者
1. SUMMARY.md > "集成点" 部分 (5 分钟) 1. SUMMARY.md > "集成点" 部分 (5 分钟)
2. INTEGRATION.md (60 分钟) 2. INTEGRATION.md (60 分钟)
3. QUICK_REFERENCE.md > "JWT Manager API" (10 分钟) 3. QUICK_REFERENCE.md > "JWT Manager API" (10 分钟)
4. 代码实现 (2-3 小时) 4. 代码实现 (2-3 小时)
5. 单元测试 (INTEGRATION.md 第9部分) 5. 单元测试 (INTEGRATION.md 第9部分)
**总计:** ~4 小时 **总计:** ~4 小时
### 对于安全/合规人员 ### 对于安全/合规人员
1. SUMMARY.md (5 分钟) 1. SUMMARY.md (5 分钟)
2. ENCRYPTION.md (30 分钟) 2. ENCRYPTION.md (30 分钟)
3. DEPLOYMENT.md > 安全最佳实践 (15 分钟) 3. DEPLOYMENT.md > 安全最佳实践 (15 分钟)
4. VERIFICATION.md 第9部分 (10 分钟) 4. VERIFICATION.md 第9部分 (10 分钟)
**总计:** ~1 小时 **总计:** ~1 小时
### 对于项目经理 ### 对于项目经理
1. SUMMARY.md (5 分钟) 1. SUMMARY.md (5 分钟)
2. DEPLOYMENT.md > "部署状态示意图" (5 分钟) 2. DEPLOYMENT.md > "部署状态示意图" (5 分钟)
3. DEPLOYMENT.md > "快速部署" (2 分钟) 3. DEPLOYMENT.md > "快速部署" (2 分钟)
**总计:** ~15 分钟 **总计:** ~15 分钟
--- ---
## 🎓 学习成果预期 ## 🎓 学习成果预期
### 完成后,您将能够: ### 完成后,您将能够:
✅ 在 Kubernetes 中部署 JWT 认证系统 ✅ 在 Kubernetes 中部署 JWT 认证系统
✅ 配置 RBAC 权限控制 ✅ 配置 RBAC 权限控制
✅ 启用 ETCD 加密保护敏感数据 ✅ 启用 ETCD 加密保护敏感数据
✅ 在 Go-zero 应用中集成 JWT ✅ 在 Go-zero 应用中集成 JWT
✅ 实现令牌刷新和撤销 ✅ 实现令牌刷新和撤销
✅ 诊断和排查常见问题 ✅ 诊断和排查常见问题
✅ 执行密钥轮换和灾难恢复 ✅ 执行密钥轮换和灾难恢复
--- ---
## 🆘 求助指南 ## 🆘 求助指南
### 第一步:找到相关文档 ### 第一步:找到相关文档
- 浏览本索引找到相关章节 - 浏览本索引找到相关章节
- 或用 Ctrl+F 搜索关键词 - 或用 Ctrl+F 搜索关键词
### 第二步:查看文档中的相关部分 ### 第二步:查看文档中的相关部分
- DEPLOYMENT.md 的相关章节 - DEPLOYMENT.md 的相关章节
- 或 VERIFICATION.md 的故障排查部分 - 或 VERIFICATION.md 的故障排查部分
### 第三步:运行诊断命令 ### 第三步:运行诊断命令
- QUICK_REFERENCE.md 的 "故障排查" 部分 - QUICK_REFERENCE.md 的 "故障排查" 部分
- 或 VERIFICATION.md 的 "故障排查" 部分 - 或 VERIFICATION.md 的 "故障排查" 部分
### 第四步:检查日志 ### 第四步:检查日志
```bash ```bash
kubectl logs -n juwan -l app=user-rpc -f kubectl logs -n juwan -l app=user-rpc -f
kubectl logs -n juwan -l app=envoy-gateway -f kubectl logs -n juwan -l app=envoy-gateway -f
``` ```
### 第五步:查看详细文档 ### 第五步:查看详细文档
如果上述步骤未能解决,查看对应的详细文档: 如果上述步骤未能解决,查看对应的详细文档:
- 配置问题 → DEPLOYMENT.md - 配置问题 → DEPLOYMENT.md
- 权限问题 → VERIFICATION.md 第2/11部分 - 权限问题 → VERIFICATION.md 第2/11部分
- 集成问题 → INTEGRATION.md - 集成问题 → INTEGRATION.md
- 加密问题 → ENCRYPTION.md - 加密问题 → ENCRYPTION.md
--- ---
## 📞 文档反馈 ## 📞 文档反馈
如果您发现: 如果您发现:
- ❌ 文档不清楚 - ❌ 文档不清楚
- ❌ 命令不工作 - ❌ 命令不工作
- ❌ 信息缺失或过时 - ❌ 信息缺失或过时
- ❌ 错别字或格式问题 - ❌ 错别字或格式问题
请在相应的 `.md` 文件中标记,或提交更新建议。 请在相应的 `.md` 文件中标记,或提交更新建议。
--- ---
## 📌 关键概念快速链接 ## 📌 关键概念快速链接
| 概念 | 详见 | | 概念 | 详见 |
|-----|------| |-----|------|
| JWT 令牌生命周期 | SUMMARY.md "关键特性" | | JWT 令牌生命周期 | SUMMARY.md "关键特性" |
| Redis 双键结构 | SUMMARY.md "关键特性" | | Redis 双键结构 | SUMMARY.md "关键特性" |
| RBAC 权限隔离 | SUMMARY.md "关键特性" | | RBAC 权限隔离 | SUMMARY.md "关键特性" |
| CSRF 防护 | SUMMARY.md "关键特性" | | CSRF 防护 | SUMMARY.md "关键特性" |
| ETCD 加密 | ENCRYPTION.md | | ETCD 加密 | ENCRYPTION.md |
| 错误处理 | INTEGRATION.md 第8部分 | | 错误处理 | INTEGRATION.md 第8部分 |
| 密钥轮换 | DEPLOYMENT.md "安全最佳实践" | | 密钥轮换 | DEPLOYMENT.md "安全最佳实践" |
| 灾难恢复 | DEPLOYMENT.md "灾难恢复" | | 灾难恢复 | DEPLOYMENT.md "灾难恢复" |
--- ---
## ✨ 文档特性 ## ✨ 文档特性
**模块化** - 每个文档独立,但相互链接 **模块化** - 每个文档独立,但相互链接
**分层** - 从快速概览到深度细节 **分层** - 从快速概览到深度细节
**实践导向** - 包含实际命令和代码示例 **实践导向** - 包含实际命令和代码示例
**完整性** - 覆盖部署、验证、维护、故障排查 **完整性** - 覆盖部署、验证、维护、故障排查
**易查找** - 目录、索引、速查表 **易查找** - 目录、索引、速查表
--- ---
**开始阅读:** 👉 [SUMMARY.md](./SUMMARY.md) **开始阅读:** 👉 [SUMMARY.md](./SUMMARY.md)
或根据您的角色选择: 或根据您的角色选择:
| 角色 | 开始文档 | 预计时间 | | 角色 | 开始文档 | 预计时间 |
|-----|--------|--------| |-----|--------|--------|
| DevOps/运维 | [DEPLOYMENT.md](./DEPLOYMENT.md) | 1-2 小时 | | DevOps/运维 | [DEPLOYMENT.md](./DEPLOYMENT.md) | 1-2 小时 |
| 应用开发 | [INTEGRATION.md](../api/INTEGRATION.md) | 2-3 小时 | | 应用开发 | [INTEGRATION.md](../api/INTEGRATION.md) | 2-3 小时 |
| 安全审查 | [ENCRYPTION.md](./ENCRYPTION.md) | 30 分钟 | | 安全审查 | [ENCRYPTION.md](./ENCRYPTION.md) | 30 分钟 |
| 项目经理 | [SUMMARY.md](./SUMMARY.md) | 15 分钟 | | 项目经理 | [SUMMARY.md](./SUMMARY.md) | 15 分钟 |
| 新手 | [README.md](./README.md) → [SUMMARY.md](./SUMMARY.md) | 15-20 分钟 | | 新手 | [README.md](./README.md) → [SUMMARY.md](./SUMMARY.md) | 15-20 分钟 |
+350 -350
View File
@@ -1,350 +1,350 @@
# JWT + ETCD 加密系统 - 快速参考卡片 # JWT + ETCD 加密系统 - 快速参考卡片
## 一页速查表 ## 一页速查表
### 部署命令 ### 部署命令
```bash ```bash
# 创建 Secret 和 RBAC # 创建 Secret 和 RBAC
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
# 更新 Deployments # 更新 Deployments
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
# 验证部署 # 验证部署
kubectl get secret jwt-secret -n juwan kubectl get secret jwt-secret -n juwan
kubectl get sa user-rpc envoy-gateway -n juwan kubectl get sa user-rpc envoy-gateway -n juwan
kubectl get role jwt-secret-reader -n juwan kubectl get role jwt-secret-reader -n juwan
kubectl get pods -n juwan -l app=user-rpc kubectl get pods -n juwan -l app=user-rpc
kubectl get pods -n juwan -l app=envoy-gateway kubectl get pods -n juwan -l app=envoy-gateway
``` ```
### 权限验证 ### 权限验证
```bash ```bash
# user-rpc 可以读 jwt-secret # user-rpc 可以读 jwt-secret
kubectl auth can-i get secrets \ kubectl auth can-i get secrets \
--as=system:serviceaccount:juwan:user-rpc \ --as=system:serviceaccount:juwan:user-rpc \
--resource-name=jwt-secret -n juwan --resource-name=jwt-secret -n juwan
# 预期: yes # 预期: yes
# 其他 SA 无法读 jwt-secret # 其他 SA 无法读 jwt-secret
kubectl auth can-i get secrets \ kubectl auth can-i get secrets \
--as=system:serviceaccount:juwan:default \ --as=system:serviceaccount:juwan:default \
--resource-name=jwt-secret -n juwan --resource-name=jwt-secret -n juwan
# 预期: no # 预期: no
``` ```
### 日志查看 ### 日志查看
```bash ```bash
# user-rpc 日志 # user-rpc 日志
kubectl logs -n juwan -l app=user-rpc -f kubectl logs -n juwan -l app=user-rpc -f
# Envoy 日志 # Envoy 日志
kubectl logs -n juwan -l app=envoy-gateway -f kubectl logs -n juwan -l app=envoy-gateway -f
# 特定 Pod 日志 # 特定 Pod 日志
kubectl logs -n juwan <pod-name> --all-containers=true -f kubectl logs -n juwan <pod-name> --all-containers=true -f
``` ```
### 环境变量验证 ### 环境变量验证
```bash ```bash
# 检查 JWT_SECRET_KEY 已注入 # 检查 JWT_SECRET_KEY 已注入
kubectl exec -it $(kubectl get pod -n juwan -l app=user-rpc -o name | head -1) \ kubectl exec -it $(kubectl get pod -n juwan -l app=user-rpc -o name | head -1) \
-n juwan -- env | grep JWT_SECRET_KEY -n juwan -- env | grep JWT_SECRET_KEY
``` ```
### Redis 验证 ### Redis 验证
```bash ```bash
# 连接到 Redis Cluster # 连接到 Redis Cluster
kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \ kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \
redis-cli -h user-redis.juwan:6379 -c CLUSTER INFO redis-cli -h user-redis.juwan:6379 -c CLUSTER INFO
# 测试键操作 # 测试键操作
kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \ kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \
redis-cli -h user-redis.juwan:6379 -c GET jwt:user:test-user-id redis-cli -h user-redis.juwan:6379 -c GET jwt:user:test-user-id
``` ```
### ETCD 加密配置 ### ETCD 加密配置
```bash ```bash
# 1. 在 Control Plane 节点生成密钥 # 1. 在 Control Plane 节点生成密钥
head -c 32 /dev/urandom | base64 head -c 32 /dev/urandom | base64
# 2. 编辑 kube-apiserver 清单 # 2. 编辑 kube-apiserver 清单
sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml
# 添加参数: # 添加参数:
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# 3. 创建加密配置文件 # 3. 创建加密配置文件
cat <<EOF | sudo tee /etc/kubernetes/encryption-config.yaml cat <<EOF | sudo tee /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1 apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration kind: EncryptionConfiguration
resources: resources:
- resources: - resources:
- secrets - secrets
providers: providers:
- aescbc: - aescbc:
keys: keys:
- name: key1 - name: key1
secret: <BASE64_KEY> secret: <BASE64_KEY>
- identity: {} - identity: {}
EOF EOF
# 4. 验证加密 # 4. 验证加密
kubectl create secret generic test-encryption -n juwan --from-literal=key=value kubectl create secret generic test-encryption -n juwan --from-literal=key=value
sudo ETCDCTL_API=3 etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt \ sudo ETCDCTL_API=3 etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \ --key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \
--endpoints=127.0.0.1:2379 \ --endpoints=127.0.0.1:2379 \
get /registry/secrets/juwan/test-encryption | od -A x -t x1z get /registry/secrets/juwan/test-encryption | od -A x -t x1z
``` ```
### 故障排查 ### 故障排查
```bash ```bash
# Pod 无法启动?查看事件 # Pod 无法启动?查看事件
kubectl describe pod <pod-name> -n juwan kubectl describe pod <pod-name> -n juwan
# 权限被拒绝?检查 RBAC # 权限被拒绝?检查 RBAC
kubectl get rolebinding -n juwan -o wide kubectl get rolebinding -n juwan -o wide
kubectl describe rolebinding jwt-secret-reader-user-rpc -n juwan kubectl describe rolebinding jwt-secret-reader-user-rpc -n juwan
# 无法挂载 Secret?检查 Secret 存在性 # 无法挂载 Secret?检查 Secret 存在性
kubectl get secret jwt-secret -n juwan -o yaml kubectl get secret jwt-secret -n juwan -o yaml
# Redis 连接错误?测试连通性 # Redis 连接错误?测试连通性
kubectl exec -it <user-rpc-pod> -n juwan -- \ kubectl exec -it <user-rpc-pod> -n juwan -- \
redis-cli -h user-redis.juwan:6379 PING redis-cli -h user-redis.juwan:6379 PING
``` ```
## JWT Manager API 速查 ## JWT Manager API 速查
### JwtManager 方法 ### JwtManager 方法
```go ```go
// 生成新令牌 // 生成新令牌
token, err := svcCtx.JwtManager.New(ctx, userID, email, name) token, err := svcCtx.JwtManager.New(ctx, userID, email, name)
// 验证令牌 // 验证令牌
claims, err := svcCtx.JwtManager.Valid(ctx, token) claims, err := svcCtx.JwtManager.Valid(ctx, token)
// 刷新令牌(如果过期但 Redis 仍有数据) // 刷新令牌(如果过期但 Redis 仍有数据)
newToken, err := svcCtx.JwtManager.Renew(ctx, token) newToken, err := svcCtx.JwtManager.Renew(ctx, token)
// 提取声明(不验证签名) // 提取声明(不验证签名)
claims, err := svcCtx.JwtManager.Extract(ctx, token) claims, err := svcCtx.JwtManager.Extract(ctx, token)
// 检查令牌是否存在于 Redis // 检查令牌是否存在于 Redis
exists, err := svcCtx.JwtManager.Exists(ctx, token) exists, err := svcCtx.JwtManager.Exists(ctx, token)
// 撤销令牌(登出) // 撤销令牌(登出)
err := svcCtx.JwtManager.Revoke(ctx, userID, token) err := svcCtx.JwtManager.Revoke(ctx, userID, token)
// 获取用户当前令牌 // 获取用户当前令牌
token, err := svcCtx.JwtManager.GetUserToken(ctx, userID) token, err := svcCtx.JwtManager.GetUserToken(ctx, userID)
// 将声明转换为载荷 // 将声明转换为载荷
payload := svcCtx.JwtManager.ClaimsToPayload(claims) payload := svcCtx.JwtManager.ClaimsToPayload(claims)
``` ```
## 配置文件位置 ## 配置文件位置
| 配置 | 位置 | 关键参数 | | 配置 | 位置 | 关键参数 |
|-----|------|--------| |-----|------|--------|
| JWT Secret | `deploy/k8s/secrets/jwt-secret.yaml` | `secret-key` | | JWT Secret | `deploy/k8s/secrets/jwt-secret.yaml` | `secret-key` |
| user-rpc 配置 | `app/users/rpc/etc/pb.yaml` | `JWT.SecretKey`, `REDIS_HOST` | | user-rpc 配置 | `app/users/rpc/etc/pb.yaml` | `JWT.SecretKey`, `REDIS_HOST` |
| Envoy 配置 | `deploy/k8s/envoy/envoy.yaml` | CSRF 验证 Lua 代码 | | Envoy 配置 | `deploy/k8s/envoy/envoy.yaml` | CSRF 验证 Lua 代码 |
| ETCD 加密 | `/etc/kubernetes/encryption-config.yaml`Control Plane | `secret` (32字节密钥) | | ETCD 加密 | `/etc/kubernetes/encryption-config.yaml`Control Plane | `secret` (32字节密钥) |
## 关键参数速查 ## 关键参数速查
```yaml ```yaml
# JWT 令牌有效期 # JWT 令牌有效期
Token Exp: 7 days Token Exp: 7 days
# Redis 存储 TTL # Redis 存储 TTL
Redis TTL: 30 days Redis TTL: 30 days
# 可刷新时间窗口 # 可刷新时间窗口
Refresh Window: 30 days - 7 days = 23 days Refresh Window: 30 days - 7 days = 23 days
# CSRF Token 位置 # CSRF Token 位置
Cookie: csrf_token=... Cookie: csrf_token=...
Header: X-CSRF-Token: ... Header: X-CSRF-Token: ...
# ETCD 加密算法 # ETCD 加密算法
Algorithm: AES-CBC Algorithm: AES-CBC
Key Size: 256 bits (32 bytes) Key Size: 256 bits (32 bytes)
Encoding: Base64 Encoding: Base64
# Secret 挂载方式 # Secret 挂载方式
Method: volumeMount (read-only) Method: volumeMount (read-only)
Method: valueFrom.secretKeyRef Method: valueFrom.secretKeyRef
``` ```
## 常见问题速查 ## 常见问题速查
| 问题 | 排查命令 | 解决方案 | | 问题 | 排查命令 | 解决方案 |
|-----|--------|--------| |-----|--------|--------|
| Pod 无法启动 | `kubectl describe pod` | 检查 Secret/RBAC | | Pod 无法启动 | `kubectl describe pod` | 检查 Secret/RBAC |
| 权限被拒绝 | `kubectl auth can-i get secrets` | 验证 RBAC 绑定 | | 权限被拒绝 | `kubectl auth can-i get secrets` | 验证 RBAC 绑定 |
| Redis 连接失败 | `redis-cli PING` | 检查 Redis Pods | | Redis 连接失败 | `redis-cli PING` | 检查 Redis Pods |
| JWT 验证失败 | 查看 Pod 日志 | 检查 Redis 中的令牌 | | JWT 验证失败 | 查看 Pod 日志 | 检查 Redis 中的令牌 |
| CSRF 验证失败 | 查看 Envoy 日志 | 检查 Cookie/Header 匹配 | | CSRF 验证失败 | 查看 Envoy 日志 | 检查 Cookie/Header 匹配 |
| ETCD 加密失败 | `kubectl get secret` | 检查 kube-apiserver 启动参数 | | ETCD 加密失败 | `kubectl get secret` | 检查 kube-apiserver 启动参数 |
## 部署检查清单 (5分钟版) ## 部署检查清单 (5分钟版)
```bash ```bash
# 第1步: 部署 Secret (10秒) # 第1步: 部署 Secret (10秒)
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml && sleep 5 kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml && sleep 5
# 第2步: 验证 Secret (10秒) # 第2步: 验证 Secret (10秒)
kubectl get secret jwt-secret -n juwan && echo "✓ Secret 已创建" kubectl get secret jwt-secret -n juwan && echo "✓ Secret 已创建"
# 第3步: 验证 RBAC (10秒) # 第3步: 验证 RBAC (10秒)
kubectl get role jwt-secret-reader -n juwan && echo "✓ RBAC 已配置" kubectl get role jwt-secret-reader -n juwan && echo "✓ RBAC 已配置"
# 第4步: 更新 Deployments (20秒) # 第4步: 更新 Deployments (20秒)
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
# 第5步: 等待 Pods 启动 (30秒) # 第5步: 等待 Pods 启动 (30秒)
kubectl rollout status deployment/user-rpc -n juwan kubectl rollout status deployment/user-rpc -n juwan
kubectl rollout status deployment/envoy-gateway -n juwan kubectl rollout status deployment/envoy-gateway -n juwan
# 第6步: 快速功能测试 (2分钟) # 第6步: 快速功能测试 (2分钟)
# 创建一个令牌并验证可读取 # 创建一个令牌并验证可读取
REDIS_POD=$(kubectl get pod -n juwan -l redis=user-redis -o name | head -1) REDIS_POD=$(kubectl get pod -n juwan -l redis=user-redis -o name | head -1)
kubectl exec -it $REDIS_POD -n juwan -- redis-cli KEYS "jwt:*" kubectl exec -it $REDIS_POD -n juwan -- redis-cli KEYS "jwt:*"
``` ```
## 密钥轮换步骤 ## 密钥轮换步骤
```bash ```bash
# 1. 生成新密钥 # 1. 生成新密钥
NEW_KEY=$(head -c 32 /dev/urandom | base64) NEW_KEY=$(head -c 32 /dev/urandom | base64)
# 2. 更新 Secret # 2. 更新 Secret
kubectl create secret generic jwt-secret \ kubectl create secret generic jwt-secret \
--from-literal=secret-key=$NEW_KEY \ --from-literal=secret-key=$NEW_KEY \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
# 3. 重启 Pods(自动挂载新 Secret # 3. 重启 Pods(自动挂载新 Secret
kubectl rollout restart deployment/user-rpc -n juwan kubectl rollout restart deployment/user-rpc -n juwan
kubectl rollout restart deployment/envoy-gateway -n juwan kubectl rollout restart deployment/envoy-gateway -n juwan
# 4. 等待 Pods 启动 # 4. 等待 Pods 启动
kubectl rollout status deployment/user-rpc -n juwan kubectl rollout status deployment/user-rpc -n juwan
kubectl rollout status deployment/envoy-gateway -n juwan kubectl rollout status deployment/envoy-gateway -n juwan
# 5. 旧令牌现在需要刷新或重新登录 # 5. 旧令牌现在需要刷新或重新登录
``` ```
## 文档地图 ## 文档地图
``` ```
deploy/k8s/secrets/ deploy/k8s/secrets/
├── jwt-secret.yaml ← Secrets + RBAC 配置 ├── jwt-secret.yaml ← Secrets + RBAC 配置
├── README.md ← 开始阅读(快速指南) ├── README.md ← 开始阅读(快速指南)
├── SUMMARY.md ← 本文件(系统概览) ├── SUMMARY.md ← 本文件(系统概览)
├── DEPLOYMENT.md ← 详细部署步骤(12步) ├── DEPLOYMENT.md ← 详细部署步骤(12步)
├── ENCRYPTION.md ← ETCD 加密详细指南 ├── ENCRYPTION.md ← ETCD 加密详细指南
├── VERIFICATION.md ← 完整验证清单(12部分) ├── VERIFICATION.md ← 完整验证清单(12部分)
└── QUICK_REFERENCE.md ← 本快速参考卡片 └── QUICK_REFERENCE.md ← 本快速参考卡片
app/users/api/ app/users/api/
└── INTEGRATION.md ← JWT 代码集成指南 └── INTEGRATION.md ← JWT 代码集成指南
app/users/rpc/ app/users/rpc/
├── internal/utils/jwt.go ← JwtManager 实现 ├── internal/utils/jwt.go ← JwtManager 实现
├── internal/config/config.go ← JWT 配置 ├── internal/config/config.go ← JWT 配置
├── internal/svc/serviceContext.go ← 依赖注入 ├── internal/svc/serviceContext.go ← 依赖注入
└── etc/pb.yaml ← 运行时配置 └── etc/pb.yaml ← 运行时配置
``` ```
## 关键时间点 ## 关键时间点
| 阶段 | 时间 | 操作 | | 阶段 | 时间 | 操作 |
|-----|------|------| |-----|------|------|
| 令牌签发 | T0 | 生成 JWT,过期时间 = T0 + 7天 | | 令牌签发 | T0 | 生成 JWT,过期时间 = T0 + 7天 |
| | | 在 Redis 存储,TTL = 30天 | | | | 在 Redis 存储,TTL = 30天 |
| Token 过期 | T0 + 7天 | JWT 验证失败 | | Token 过期 | T0 + 7天 | JWT 验证失败 |
| 令牌刷新 | T0 + 7天到T0 + 30天 | 如果 Redis 仍有数据,生成新令牌 | | 令牌刷新 | T0 + 7天到T0 + 30天 | 如果 Redis 仍有数据,生成新令牌 |
| 完全失效 | T0 + 30天 | Redis 删除,无法再刷新 | | 完全失效 | T0 + 30天 | Redis 删除,无法再刷新 |
| 重新登录 | T0 + 30天+ | 用户需要重新登录 | | 重新登录 | T0 + 30天+ | 用户需要重新登录 |
## 性能提示 ## 性能提示
```bash ```bash
# 高并发下优化 Redis 连接 # 高并发下优化 Redis 连接
# 在 pb.yaml 中调整: # 在 pb.yaml 中调整:
CacheConf: CacheConf:
- Host: "user-redis.juwan:6379" - Host: "user-redis.juwan:6379"
Type: "cluster" Type: "cluster"
MaxConnections: 100 MaxConnections: 100
ConnectionPoolSize: 50 ConnectionPoolSize: 50
# 监控 JWT 验证吞吐量 # 监控 JWT 验证吞吐量
# 在 Prometheus 查询: # 在 Prometheus 查询:
rate(jwt_validations_total[5m]) rate(jwt_validations_total[5m])
rate(jwt_refresh_total[5m]) rate(jwt_refresh_total[5m])
``` ```
## 安全提示 ## 安全提示
✅ **必做** ✅ **必做**
- [ ] 定期轮换 JWT 秘钥(季度) - [ ] 定期轮换 JWT 秘钥(季度)
- [ ] 定期轮换 ETCD 加密密钥(年度) - [ ] 定期轮换 ETCD 加密密钥(年度)
- [ ] 备份加密密钥到安全位置 - [ ] 备份加密密钥到安全位置
- [ ] 启用审计日志 - [ ] 启用审计日志
- [ ] 监控异常的令牌验证失败 - [ ] 监控异常的令牌验证失败
❌ **禁止** ❌ **禁止**
- [ ] 不要在日志中输出 JWT 秘钥 - [ ] 不要在日志中输出 JWT 秘钥
- [ ] 不要在代码库中存储密钥 - [ ] 不要在代码库中存储密钥
- [ ] 不要发送明文密钥到 Slack/Email - [ ] 不要发送明文密钥到 Slack/Email
- [ ] 不要在多个环境间共享密钥 - [ ] 不要在多个环境间共享密钥
## 版本信息 ## 版本信息
``` ```
Kubernetes: 1.24+ Kubernetes: 1.24+
Go-zero: v1.10.0+ Go-zero: v1.10.0+
Redis: 7.0+ Redis: 7.0+
ETCD: 3.5+ ETCD: 3.5+
Envoy: v1.32.2+ Envoy: v1.32.2+
``` ```
## 支持和反馈 ## 支持和反馈
遇到问题?按优先级检查: 遇到问题?按优先级检查:
1. **运行验证脚本** 1. **运行验证脚本**
```bash ```bash
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
./deploy/k8s/secrets/verify-jwt-setup.sh ./deploy/k8s/secrets/verify-jwt-setup.sh
``` ```
2. **查看日志** 2. **查看日志**
```bash ```bash
kubectl logs -n juwan -l app=user-rpc -f kubectl logs -n juwan -l app=user-rpc -f
``` ```
3. **阅读 VERIFICATION.md** 3. **阅读 VERIFICATION.md**
- 第1-5部分: 基础配置 - 第1-5部分: 基础配置
- 第6-8部分: 网络和监控 - 第6-8部分: 网络和监控
- 第9部分: ETCD 加密 - 第9部分: ETCD 加密
- 第11部分: 故障排查 - 第11部分: 故障排查
4. **详细指南** 4. **详细指南**
- DEPLOYMENT.md - 完整步骤 - DEPLOYMENT.md - 完整步骤
- INTEGRATION.md - 代码集成 - INTEGRATION.md - 代码集成
- ENCRYPTION.md - 加密配置 - ENCRYPTION.md - 加密配置
+148 -148
View File
@@ -1,148 +1,148 @@
# JWT Secret Management # JWT Secret Management
This directory contains secure configuration for JWT secret key management. This directory contains secure configuration for JWT secret key management.
## Files ## Files
- `jwt-secret.yaml`: Kubernetes Secret + ServiceAccount + RBAC rules - `jwt-secret.yaml`: Kubernetes Secret + ServiceAccount + RBAC rules
- `ENCRYPTION.md`: Guide for enabling ETCD static encryption at rest - `ENCRYPTION.md`: Guide for enabling ETCD static encryption at rest
## Quick Start ## Quick Start
### 1. Create the Secret and RBAC ### 1. Create the Secret and RBAC
```bash ```bash
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
``` ```
This will create: This will create:
- Secret `jwt-secret` in namespace `juwan` containing the JWT secret key - Secret `jwt-secret` in namespace `juwan` containing the JWT secret key
- ServiceAccount `user-rpc` in namespace `juwan` - ServiceAccount `user-rpc` in namespace `juwan`
- ServiceAccount `envoy-gateway` in namespace `juwan` - ServiceAccount `envoy-gateway` in namespace `juwan`
- Role `jwt-secret-reader` that allows reading only `jwt-secret` - Role `jwt-secret-reader` that allows reading only `jwt-secret`
- RoleBindings to grant both ServiceAccounts read permission on the secret - RoleBindings to grant both ServiceAccounts read permission on the secret
### 2. Update user-rpc Deployment ### 2. Update user-rpc Deployment
Update `deploy/k8s/service/user/user-rpc.yaml` to: Update `deploy/k8s/service/user/user-rpc.yaml` to:
1. Set the serviceAccountName: 1. Set the serviceAccountName:
```yaml ```yaml
spec: spec:
template: template:
spec: spec:
serviceAccountName: user-rpc serviceAccountName: user-rpc
``` ```
2. Add environment variable to load JWT secret: 2. Add environment variable to load JWT secret:
```yaml ```yaml
spec: spec:
template: template:
spec: spec:
containers: containers:
- name: user-rpc - name: user-rpc
env: env:
- name: JWT_SECRET_KEY - name: JWT_SECRET_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: jwt-secret name: jwt-secret
key: secret-key key: secret-key
``` ```
### 3. Update envoy-gateway Deployment ### 3. Update envoy-gateway Deployment
Update `deploy/k8s/envoy/envoy.yaml` to: Update `deploy/k8s/envoy/envoy.yaml` to:
1. Set the serviceAccountName: 1. Set the serviceAccountName:
```yaml ```yaml
spec: spec:
template: template:
spec: spec:
serviceAccountName: envoy-gateway serviceAccountName: envoy-gateway
``` ```
2. Add environment variable or mount Secret: 2. Add environment variable or mount Secret:
```yaml ```yaml
volumeMounts: volumeMounts:
- name: jwt-secret - name: jwt-secret
mountPath: /etc/jwt mountPath: /etc/jwt
readOnly: true readOnly: true
volumes: volumes:
- name: jwt-secret - name: jwt-secret
secret: secret:
secretName: jwt-secret secretName: jwt-secret
defaultMode: 0400 defaultMode: 0400
``` ```
Then reference it in the Envoy config: Then reference it in the Envoy config:
```yaml ```yaml
data: data:
envoy.yaml: | envoy.yaml: |
# Read JWT secret from /etc/jwt/secret-key # Read JWT secret from /etc/jwt/secret-key
``` ```
### 4. Enable ETCD Encryption ### 4. Enable ETCD Encryption
Follow the guide in `ENCRYPTION.md` to enable static encryption at rest for all secrets in ETCD. Follow the guide in `ENCRYPTION.md` to enable static encryption at rest for all secrets in ETCD.
## Security Considerations ## Security Considerations
### Least Privilege ### Least Privilege
- Only `user-rpc` and `envoy-gateway` can read the JWT secret - Only `user-rpc` and `envoy-gateway` can read the JWT secret
- No other services or users have access - No other services or users have access
- The Role allows reading **only** the `jwt-secret`, not other secrets - The Role allows reading **only** the `jwt-secret`, not other secrets
### Encryption at Rest ### Encryption at Rest
- With ETCD encryption enabled, the secret is encrypted when stored on disk - With ETCD encryption enabled, the secret is encrypted when stored on disk
- Even if someone gains access to the ETCD database files, they cannot read the secret without the encryption key - Even if someone gains access to the ETCD database files, they cannot read the secret without the encryption key
### Secret Rotation ### Secret Rotation
To rotate the JWT secret key: To rotate the JWT secret key:
1. Generate a new key 1. Generate a new key
2. Update the Secret: 2. Update the Secret:
```bash ```bash
kubectl create secret generic jwt-secret --from-literal=secret-key=NEW_KEY --dry-run=client -o yaml | kubectl apply -f - kubectl create secret generic jwt-secret --from-literal=secret-key=NEW_KEY --dry-run=client -o yaml | kubectl apply -f -
``` ```
3. Pod mounts/env vars will be updated automatically within a few minutes 3. Pod mounts/env vars will be updated automatically within a few minutes
4. Old tokens will become invalid (you may need to log users out) 4. Old tokens will become invalid (you may need to log users out)
## Production Checklist ## Production Checklist
- [ ] ETCD encryption enabled (see ENCRYPTION.md) - [ ] ETCD encryption enabled (see ENCRYPTION.md)
- [ ] JWT secret key changed from default - [ ] JWT secret key changed from default
- [ ] Both user-rpc and envoy-gateway Deployments use correct serviceAccountName - [ ] Both user-rpc and envoy-gateway Deployments use correct serviceAccountName
- [ ] Both Deployments load the secret via environment variable or volume mount - [ ] Both Deployments load the secret via environment variable or volume mount
- [ ] Regular secret rotation policy implemented - [ ] Regular secret rotation policy implemented
- [ ] Secret backup stored in secure location (encrypted) - [ ] Secret backup stored in secure location (encrypted)
- [ ] RBAC audit logging enabled to track secret access - [ ] RBAC audit logging enabled to track secret access
## Troubleshooting ## Troubleshooting
### Cannot read jwt-secret ### Cannot read jwt-secret
Check if the Pod is using the correct ServiceAccount: Check if the Pod is using the correct ServiceAccount:
```bash ```bash
kubectl get deployment user-rpc -o yaml | grep serviceAccountName kubectl get deployment user-rpc -o yaml | grep serviceAccountName
``` ```
### Secret not being mounted ### Secret not being mounted
Verify the Secret exists: Verify the Secret exists:
```bash ```bash
kubectl get secret jwt-secret -n juwan kubectl get secret jwt-secret -n juwan
``` ```
Check Pod logs for mounting errors: Check Pod logs for mounting errors:
```bash ```bash
kubectl logs -l app=user-rpc -n juwan kubectl logs -l app=user-rpc -n juwan
``` ```
### Permission denied error ### Permission denied error
Verify RBAC binding: Verify RBAC binding:
```bash ```bash
kubectl get rolebinding -n juwan kubectl get rolebinding -n juwan
kubectl get role jwt-secret-reader -n juwan kubectl get role jwt-secret-reader -n juwan
``` ```
+366 -366
View File
@@ -1,366 +1,366 @@
# JWT 认证系统 + ETCD 加密 - 完整部署总结 # JWT 认证系统 + ETCD 加密 - 完整部署总结
## 项目概览 ## 项目概览
这个项目为微服务提供了一个完整的 JWT 认证系统,包括: 这个项目为微服务提供了一个完整的 JWT 认证系统,包括:
1. **JWT 令牌管理** - 令牌生成、验证、刷新和撤销 1. **JWT 令牌管理** - 令牌生成、验证、刷新和撤销
2. **Redis Cluster 存储** - 令牌交换缓存(30天TTL)和用户会话管理 2. **Redis Cluster 存储** - 令牌交换缓存(30天TTL)和用户会话管理
3. **RBAC 权限控制** - 限制只有 user-rpc 和 envoy-gateway 服务可以访问 JWT 秘钥 3. **RBAC 权限控制** - 限制只有 user-rpc 和 envoy-gateway 服务可以访问 JWT 秘钥
4. **ETCD 加密** - 在 Kubernetes 集群中对所有 Secrets 进行加密 4. **ETCD 加密** - 在 Kubernetes 集群中对所有 Secrets 进行加密
5. **网关保护** - Envoy 网关处理 CSRF 防护和请求路由 5. **网关保护** - Envoy 网关处理 CSRF 防护和请求路由
## 创建的文件清单 ## 创建的文件清单
### 部署配置文件 ### 部署配置文件
#### `/deploy/k8s/secrets/` #### `/deploy/k8s/secrets/`
| 文件 | 说明 | 关键内容 | | 文件 | 说明 | 关键内容 |
|-----|------|--------| |-----|------|--------|
| `jwt-secret.yaml` | Secret + RBAC 配置 | 包含JWT秘钥、ServiceAccounts、Role、RoleBindings | | `jwt-secret.yaml` | Secret + RBAC 配置 | 包含JWT秘钥、ServiceAccounts、Role、RoleBindings |
| `README.md` | 快速参考指南 | Secret 创建和 Deployment 更新说明 | | `README.md` | 快速参考指南 | Secret 创建和 Deployment 更新说明 |
| `DEPLOYMENT.md` | 详细部署步骤 | 12个部署步骤,包括ETCD加密配置 | | `DEPLOYMENT.md` | 详细部署步骤 | 12个部署步骤,包括ETCD加密配置 |
| `ENCRYPTION.md` | ETCD加密完整指南 | 密钥生成、配置修改、验证流程 | | `ENCRYPTION.md` | ETCD加密完整指南 | 密钥生成、配置修改、验证流程 |
| `VERIFICATION.md` | 验证清单 | 12个部分的完整验证脚本和检查项 | | `VERIFICATION.md` | 验证清单 | 12个部分的完整验证脚本和检查项 |
### 应用代码更新 ### 应用代码更新
| 文件路径 | 修改内容 | | 文件路径 | 修改内容 |
|---------|--------| |---------|--------|
| `/app/users/rpc/internal/utils/jwt.go` | JwtManager 实现(已存在) | | `/app/users/rpc/internal/utils/jwt.go` | JwtManager 实现(已存在) |
| `/app/users/rpc/internal/config/config.go` | JwtConfig 结构体(已存在) | | `/app/users/rpc/internal/config/config.go` | JwtConfig 结构体(已存在) |
| `/app/users/rpc/internal/svc/serviceContext.go` | Redis Cluster + JwtManager 依赖注入(已存在) | | `/app/users/rpc/internal/svc/serviceContext.go` | Redis Cluster + JwtManager 依赖注入(已存在) |
| `/app/users/rpc/etc/pb.yaml` | JWT 和 Redis Cluster 配置(已存在) | | `/app/users/rpc/etc/pb.yaml` | JWT 和 Redis Cluster 配置(已存在) |
| `/deploy/k8s/service/user/user-rpc.yaml` | ✅ **已更新** - 添加 serviceAccountName + JWT_SECRET_KEY 环境变量 | | `/deploy/k8s/service/user/user-rpc.yaml` | ✅ **已更新** - 添加 serviceAccountName + JWT_SECRET_KEY 环境变量 |
| `/deploy/k8s/envoy/envoy.yaml` | ✅ **已更新** - 添加 serviceAccountName: envoy-gateway | | `/deploy/k8s/envoy/envoy.yaml` | ✅ **已更新** - 添加 serviceAccountName: envoy-gateway |
| `/app/users/api/INTEGRATION.md` | 🆕 **新建** - JWT 集成指南(interceptors, handlers, middleware | | `/app/users/api/INTEGRATION.md` | 🆕 **新建** - JWT 集成指南(interceptors, handlers, middleware |
## 部署状态示意图 ## 部署状态示意图
``` ```
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │ │ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ ┌─────────────────────────────────────────────────────────────┐ │ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ juwan Namespace │ │ │ │ juwan Namespace │ │
│ ├─────────────────────────────────────────────────────────────┤ │ │ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │ │ │ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ Secret: jwt-secret │ │ │ │ │ │ Secret: jwt-secret │ │ │
│ │ │ ├─ secret-key: <encrypted in ETCD> │ │ │ │ │ │ ├─ secret-key: <encrypted in ETCD> │ │ │
│ │ │ └─ Protected by RBAC Role + RoleBindings │ │ │ │ │ │ └─ Protected by RBAC Role + RoleBindings │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │ │ │ └────────────────────────────────────────────────────┘ │ │
│ │ △ │ │ │ │ △ │ │
│ │ │ (mounted via serviceAccountName) │ │ │ │ │ (mounted via serviceAccountName) │ │
│ │ │ │ │ │ │ │ │ │
│ │ ┌─────────────────┐ ┌──────────────────────┐ │ │ │ │ ┌─────────────────┐ ┌──────────────────────┐ │ │
│ │ │ user-rpc │ │ envoy-gateway │ │ │ │ │ │ user-rpc │ │ envoy-gateway │ │ │
│ │ │ Deployment │ │ Deployment │ │ │ │ │ │ Deployment │ │ Deployment │ │ │
│ │ ├─────────────────┤ ├──────────────────────┤ │ │ │ │ ├─────────────────┤ ├──────────────────────┤ │ │
│ │ │ SA: user-rpc │ │ SA: envoy-gateway │ │ │ │ │ │ SA: user-rpc │ │ SA: envoy-gateway │ │ │
│ │ │ Replicas: 3 │ │ Replicas: 1 │ │ │ │ │ │ Replicas: 3 │ │ Replicas: 1 │ │ │
│ │ │ Port: 9001(RPC) │ │ Port: 8080(HTTP) │ │ │ │ │ │ Port: 9001(RPC) │ │ Port: 8080(HTTP) │ │ │
│ │ │ 4001(Met) │ │ │ │ │ │ │ │ 4001(Met) │ │ │ │ │
│ │ └─────────────────┘ └──────────────────────┘ │ │ │ │ └─────────────────┘ └──────────────────────┘ │ │
│ │ │ JWT Manager │ CSRF Filter │ │ │ │ │ JWT Manager │ CSRF Filter │ │
│ │ │ (HS256 signing) │ (X-CSRF-Token) │ │ │ │ │ (HS256 signing) │ (X-CSRF-Token) │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ ┌──────▼──────────────────────────▼─────┐ │ │ │ │ ┌──────▼──────────────────────────▼─────┐ │ │
│ │ │ user-redis (RedisCluster) │ │ │ │ │ │ user-redis (RedisCluster) │ │ │
│ │ │ 3-node cluster │ │ │ │ │ │ 3-node cluster │ │ │
│ │ │ - Token exchange cache (30d TTL) │ │ │ │ │ │ - Token exchange cache (30d TTL) │ │ │
│ │ │ - User session management │ │ │ │ │ │ - User session management │ │ │
│ │ └────────────────────────────────────────┘ │ │ │ │ └────────────────────────────────────────┘ │ │
│ │ │ │ │ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────────────────────────────────────┐ │ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Control Plane (kube-apiserver) │ │ │ │ Control Plane (kube-apiserver) │ │
│ │ • ETCD 加密: AES-CBC 32-byte key │ │ │ │ • ETCD 加密: AES-CBC 32-byte key │ │
│ │ • Secrets 自动加密存储 │ │ │ │ • Secrets 自动加密存储 │ │
│ └─────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
``` ```
## 核心配置参数 ## 核心配置参数
### JWT 配置 ### JWT 配置
```yaml ```yaml
JWT: JWT:
SecretKey: "your-secret-jwt-key-change-this-in-production" SecretKey: "your-secret-jwt-key-change-this-in-production"
Issuer: "your-app-name" Issuer: "your-app-name"
# Token 有效期: 7 天 # Token 有效期: 7 天
# Redis TTL: 30 天(支持令牌刷新) # Redis TTL: 30 天(支持令牌刷新)
``` ```
### Redis Cluster ### Redis Cluster
```yaml ```yaml
RedisCluster: RedisCluster:
ClusterSize: 3 🔴 主服务器 + 2 🔵 从服务器 ClusterSize: 3 🔴 主服务器 + 2 🔵 从服务器
Address: "user-redis.juwan:6379" Address: "user-redis.juwan:6379"
HighAvailability: 自动故障转移 HighAvailability: 自动故障转移
``` ```
### RBAC 权限 ### RBAC 权限
```yaml ```yaml
Role: jwt-secret-reader Role: jwt-secret-reader
Resources: [secrets] Resources: [secrets]
ResourceNames: [jwt-secret] ResourceNames: [jwt-secret]
Verbs: [get] # 只读,无列表/创建/删除 Verbs: [get] # 只读,无列表/创建/删除
Subjects: Subjects:
- user-rpc (ServiceAccount) - user-rpc (ServiceAccount)
- envoy-gateway (ServiceAccount) - envoy-gateway (ServiceAccount)
``` ```
## 部署流程 ## 部署流程
### 快速部署(5分钟) ### 快速部署(5分钟)
```bash ```bash
# 第1步:创建 Secret 和 RBAC # 第1步:创建 Secret 和 RBAC
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
# 第2步:更新 Deployments # 第2步:更新 Deployments
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
# 第3步:验证 # 第3步:验证
./verify-jwt-setup.sh ./verify-jwt-setup.sh
``` ```
### ETCD 加密部署(需要集群管理员权限) ### ETCD 加密部署(需要集群管理员权限)
```bash ```bash
# 在 Control Plane 节点执行 # 在 Control Plane 节点执行
1. 生成 32 字节密钥 1. 生成 32 字节密钥
2. 创建 /etc/kubernetes/encryption-config.yaml 2. 创建 /etc/kubernetes/encryption-config.yaml
3. 修改 /etc/kubernetes/manifests/kube-apiserver.yaml 3. 修改 /etc/kubernetes/manifests/kube-apiserver.yaml
4. 重启 kube-apiserver 4. 重启 kube-apiserver
5. 验证加密已启用 5. 验证加密已启用
``` ```
详见:`deploy/k8s/secrets/ENCRYPTION.md` 详见:`deploy/k8s/secrets/ENCRYPTION.md`
## 关键特性 ## 关键特性
### 1. JWT 令牌生命周期 ### 1. JWT 令牌生命周期
``` ```
登录 → 生成 JWT 登录 → 生成 JWT
├─ 有效期: 7 天(exp claim ├─ 有效期: 7 天(exp claim
└─ 存储到 Redis: 30 天(TTL └─ 存储到 Redis: 30 天(TTL
Token 过期(7天+ Token 过期(7天+
├─ JWT 签名验证失败 ├─ JWT 签名验证失败
└─ 检查 Redis 是否仍有数据 └─ 检查 Redis 是否仍有数据
├─ 有 → 生成新 Token(刷新)✅ ├─ 有 → 生成新 Token(刷新)✅
└─ 无 → 令牌已过期,需要重新登录 ❌ └─ 无 → 令牌已过期,需要重新登录 ❌
``` ```
### 2. Redis 双键结构 ### 2. Redis 双键结构
``` ```
jwt:user:{userId} → {token} jwt:user:{userId} → {token}
用途: 快速查询用户当前令牌 用途: 快速查询用户当前令牌
TTL: 30 天 TTL: 30 天
jwt:token:{token} → {payload} jwt:token:{token} → {payload}
用途: 令牌验证和刷新 用途: 令牌验证和刷新
TTL: 30 天 TTL: 30 天
``` ```
### 3. CSRF 防护(Envoy 网关) ### 3. CSRF 防护(Envoy 网关)
``` ```
安全方法 (GET/HEAD/OPTIONS) 安全方法 (GET/HEAD/OPTIONS)
→ 自动生成 csrf_token → 自动生成 csrf_token
→ 返回 Set-Cookie: csrf_token=... → 返回 Set-Cookie: csrf_token=...
不安全方法 (POST/PUT/DELETE/PATCH) 不安全方法 (POST/PUT/DELETE/PATCH)
→ 检查 Cookie csrf_token → 检查 Cookie csrf_token
→ 检查 X-CSRF-Token 头 → 检查 X-CSRF-Token 头
→ 两者必须相等,否则 403 → 两者必须相等,否则 403
``` ```
### 4. 权限隔离 ### 4. 权限隔离
``` ```
Only user-rpc + envoy-gateway 可以: Only user-rpc + envoy-gateway 可以:
✅ 读 jwt-secret ✅ 读 jwt-secret
Other services 无法: Other services 无法:
❌ 列出 secrets ❌ 列出 secrets
❌ 获取 jwt-secretRBAC 拒绝) ❌ 获取 jwt-secretRBAC 拒绝)
❌ 删除 secrets ❌ 删除 secrets
``` ```
### 5. ETCD 加密 ### 5. ETCD 加密
``` ```
未加密: 未加密:
etcdctl get /registry/seca/... etcdctl get /registry/seca/...
→ secret-key: "plaintext-value" → secret-key: "plaintext-value"
已加密 (AES-CBC): 已加密 (AES-CBC):
etcdctl get /registry/secrets/... etcdctl get /registry/secrets/...
→ 二进制数据,无法读取 → 二进制数据,无法读取
``` ```
## 集成点 ## 集成点
### 1. RPC Handler(需要实现) ### 1. RPC Handler(需要实现)
```go ```go
// 在 gRPC server 中注册拦截器 // 在 gRPC server 中注册拦截器
s := grpc.NewServer( s := grpc.NewServer(
grpc.UnaryInterceptor(interceptor.JwtUnaryInterceptor(ctx)), grpc.UnaryInterceptor(interceptor.JwtUnaryInterceptor(ctx)),
) )
// 拦截器会: // 拦截器会:
// 1. 提取 Authorization 头中的 Token // 1. 提取 Authorization 头中的 Token
// 2. 调用 JwtManager.Valid() // 2. 调用 JwtManager.Valid()
// 3. 如果过期,尝试 JwtManager.Renew() // 3. 如果过期,尝试 JwtManager.Renew()
// 4. 将声明注入 context // 4. 将声明注入 context
``` ```
参考:`app/users/api/INTEGRATION.md` 第1-2章 参考:`app/users/api/INTEGRATION.md` 第1-2章
### 2. REST Endpoint(需要实现) ### 2. REST Endpoint(需要实现)
```go ```go
// 创建 JWT Middleware // 创建 JWT Middleware
protected := middleware.JwtMiddleware(svcCtx) protected := middleware.JwtMiddleware(svcCtx)
// 应用到受保护的路由 // 应用到受保护的路由
router.HandleFunc("GET /api/v1/users/me", router.HandleFunc("GET /api/v1/users/me",
protected(user.GetUserInfoHandler(svcCtx))) protected(user.GetUserInfoHandler(svcCtx)))
// Middleware 会验证 Authorization: Bearer {token} // Middleware 会验证 Authorization: Bearer {token}
``` ```
参考:`app/users/api/INTEGRATION.md` 第3-4章 参考:`app/users/api/INTEGRATION.md` 第3-4章
### 3. 登录/登出流程(需要实现) ### 3. 登录/登出流程(需要实现)
``` ```
登录: 登录:
1. 验证用户凭证(DB 查询) 1. 验证用户凭证(DB 查询)
2. JwtManager.New() → 生成令牌 2. JwtManager.New() → 生成令牌
3. 返回令牌给客户端 3. 返回令牌给客户端
登出: 登出:
1. 从上下文提取 userId 1. 从上下文提取 userId
2. JwtManager.Revoke() → 删除 Redis 中的令牌 2. JwtManager.Revoke() → 删除 Redis 中的令牌
3. 用户需要重新登录获取新令牌 3. 用户需要重新登录获取新令牌
``` ```
参考:`app/users/api/INTEGRATION.md` 第5-6章 参考:`app/users/api/INTEGRATION.md` 第5-6章
## 文档导航 ## 文档导航
| 场景 | 推荐阅读 | | 场景 | 推荐阅读 |
|-----|--------| |-----|--------|
| 第一次部署 | `README.md``DEPLOYMENT.md` | | 第一次部署 | `README.md``DEPLOYMENT.md` |
| 部署遇到问题 | `VERIFICATION.md` + `DEPLOYMENT.md` 故障排查部分 | | 部署遇到问题 | `VERIFICATION.md` + `DEPLOYMENT.md` 故障排查部分 |
| 代码集成 | `app/users/api/INTEGRATION.md` | | 代码集成 | `app/users/api/INTEGRATION.md` |
| ETCD 加密配置 | `ENCRYPTION.md` | | ETCD 加密配置 | `ENCRYPTION.md` |
| ETCD 加密验证 | `VERIFICATION.md` 第9部分 | | ETCD 加密验证 | `VERIFICATION.md` 第9部分 |
| 安全最佳实践 | `DEPLOYMENT.md` 安全最佳实践部分 | | 安全最佳实践 | `DEPLOYMENT.md` 安全最佳实践部分 |
| 灾难恢复 | `DEPLOYMENT.md` 灾难恢复部分 | | 灾难恢复 | `DEPLOYMENT.md` 灾难恢复部分 |
## 生产就绪检查清单 ## 生产就绪检查清单
- [ ] 所有 Pods 都在 Running 状态 - [ ] 所有 Pods 都在 Running 状态
- [ ] JWT Secret 已创建并正确挂载 - [ ] JWT Secret 已创建并正确挂载
- [ ] RBAC 权限验证通过 - [ ] RBAC 权限验证通过
- [ ] Redis Cluster 健康(3/3 节点) - [ ] Redis Cluster 健康(3/3 节点)
- [ ] ETCD 加密已启用(如需要) - [ ] ETCD 加密已启用(如需要)
- [ ] 监控和日志聚合正常工作 - [ ] 监控和日志聚合正常工作
- [ ] 密钥轮换计划已制定 - [ ] 密钥轮换计划已制定
- [ ] 备份和恢复流程已文档化 - [ ] 备份和恢复流程已文档化
- [ ] 安全审计日志已启用 - [ ] 安全审计日志已启用
- [ ] 端到端测试已通过 - [ ] 端到端测试已通过
## 下一步行动 ## 下一步行动
### 短期(本周) ### 短期(本周)
1. **部署 Secret 和 RBAC** 1. **部署 Secret 和 RBAC**
```bash ```bash
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
``` ```
2. **更新 Deployments** 2. **更新 Deployments**
```bash ```bash
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
kubectl apply -f deploy/k8s/envoy/envoy.yaml kubectl apply -f deploy/k8s/envoy/envoy.yaml
``` ```
3. **验证部署** 3. **验证部署**
```bash ```bash
./verify-jwt-setup.sh ./verify-jwt-setup.sh
``` ```
### 中期(本月) ### 中期(本月)
1. **实现 JWT 集成** 1. **实现 JWT 集成**
- 创建 gRPC 拦截器 - 创建 gRPC 拦截器
- 实现登录/登出端点 - 实现登录/登出端点
- 添加 JWT 中间件到 REST API - 添加 JWT 中间件到 REST API
2. **端到端测试** 2. **端到端测试**
- 测试令牌生成和验证 - 测试令牌生成和验证
- 测试令牌刷新 - 测试令牌刷新
- 测试 CSRF 防护 - 测试 CSRF 防护
### 长期(本季度) ### 长期(本季度)
1. **启用 ETCD 加密** 1. **启用 ETCD 加密**
- 按照 `ENCRYPTION.md` 配置 - 按照 `ENCRYPTION.md` 配置
- 验证所有 Secrets 都已加密 - 验证所有 Secrets 都已加密
2. **生产部署** 2. **生产部署**
- 启用审计日志 - 启用审计日志
- 配置监控和告警 - 配置监控和告警
- 制定密钥轮换政策 - 制定密钥轮换政策
## 支持 ## 支持
如遇到问题: 如遇到问题:
1. **检查日志** 1. **检查日志**
```bash ```bash
kubectl logs -n juwan -l app=user-rpc -f kubectl logs -n juwan -l app=user-rpc -f
kubectl logs -n juwan -l app=envoy-gateway -f kubectl logs -n juwan -l app=envoy-gateway -f
``` ```
2. **运行验证脚本** 2. **运行验证脚本**
```bash ```bash
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
./deploy/k8s/secrets/verify-jwt-setup.sh ./deploy/k8s/secrets/verify-jwt-setup.sh
``` ```
3. **查看详细文档** 3. **查看详细文档**
- 部署问题 → `DEPLOYMENT.md` - 部署问题 → `DEPLOYMENT.md`
- 代码集成 → `INTEGRATION.md` - 代码集成 → `INTEGRATION.md`
- ETCD 加密 → `ENCRYPTION.md` - ETCD 加密 → `ENCRYPTION.md`
- 诊断 → `VERIFICATION.md` - 诊断 → `VERIFICATION.md`
## 总结 ## 总结
这个系统为微服务提供了: 这个系统为微服务提供了:
**安全的身份验证** - JWT 令牌 + HS256 签名 **安全的身份验证** - JWT 令牌 + HS256 签名
**灵活的令牌管理** - 7天有效期,30天可刷新 **灵活的令牌管理** - 7天有效期,30天可刷新
**高可用性** - Redis Cluster 自动故障转移 **高可用性** - Redis Cluster 自动故障转移
**权限隔离** - RBAC 限制密钥访问 **权限隔离** - RBAC 限制密钥访问
**数据加密** - ETCD 加密保护敏感信息 **数据加密** - ETCD 加密保护敏感信息
**请求保护** - Envoy CSRF 双令牌验证 **请求保护** - Envoy CSRF 双令牌验证
现在可以部署并集成到应用中了! 现在可以部署并集成到应用中了!
File diff suppressed because it is too large Load Diff
+60 -60
View File
@@ -1,60 +1,60 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: jwt-secret name: jwt-secret
namespace: juwan namespace: juwan
type: Opaque type: Opaque
data: data:
# base64 encoded: your-secret-jwt-key-change-this-in-production # base64 encoded: your-secret-jwt-key-change-this-in-production
secret-key: eW91ci1zZWNyZXQtand0LWtleS1jaGFuZ2UtdGhpcy1pbi1wcm9kdWN0aW9u secret-key: eW91ci1zZWNyZXQtand0LWtleS1jaGFuZ2UtdGhpcy1pbi1wcm9kdWN0aW9u
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: envoy-gateway name: envoy-gateway
namespace: juwan namespace: juwan
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
metadata: metadata:
name: jwt-secret-reader name: jwt-secret-reader
namespace: juwan namespace: juwan
rules: rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["secrets"] resources: ["secrets"]
resourceNames: ["jwt-secret"] resourceNames: ["jwt-secret"]
verbs: ["get"] verbs: ["get"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: user-rpc-jwt-secret-reader name: user-rpc-jwt-secret-reader
namespace: juwan namespace: juwan
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: Role kind: Role
name: jwt-secret-reader name: jwt-secret-reader
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: user-rpc name: user-rpc
namespace: juwan namespace: juwan
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:
name: envoy-gateway-jwt-secret-reader name: envoy-gateway-jwt-secret-reader
namespace: juwan namespace: juwan
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: Role kind: Role
name: jwt-secret-reader name: jwt-secret-reader
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: envoy-gateway name: envoy-gateway
namespace: juwan namespace: juwan
+1153 -1153
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -1,16 +1,16 @@
{ {
"type": "module", "type": "module",
"packageManager": "npm@11.9.0+sha512.04166853ddba142ca98f86fb57b1258a7c6c59ccb82acb3cf141b77a315898acaaed47395e74f7e0c7b69c486008e68be6a6381ef1aee5a23dd82e0e61decd68", "packageManager": "npm@11.9.0+sha512.04166853ddba142ca98f86fb57b1258a7c6c59ccb82acb3cf141b77a315898acaaed47395e74f7e0c7b69c486008e68be6a6381ef1aee5a23dd82e0e61decd68",
"dependencies": { "dependencies": {
"@inquirer/prompts": "^8.2.0", "@inquirer/prompts": "^8.2.0",
"@inquirer/search": "^4.1.0", "@inquirer/search": "^4.1.0",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"glob": "^13.0.1", "glob": "^13.0.1",
"hereby": "^1.11.1", "hereby": "^1.11.1",
"postgres": "^3.4.8", "postgres": "^3.4.8",
"prompts": "^2.4.2" "prompts": "^2.4.2"
}, },
"devDependencies": { "devDependencies": {
"execa": "^9.6.1" "execa": "^9.6.1"
} }
} }