normalize line endings to LF and add envoy dockerfile in deploy/dev
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
deploy/dev/script/*.sh text eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
+127
-127
@@ -1,128 +1,128 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
dev test
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# 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
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# End of https://mrkandreev.name/snippets/gitignore-generator/#Node
|
||||
|
||||
DockerFile
|
||||
.idea
|
||||
|
||||
# Go compiled binaries
|
||||
app/*/api/api
|
||||
app/*/rpc/rpc
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
dev test
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# 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
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# End of https://mrkandreev.name/snippets/gitignore-generator/#Node
|
||||
|
||||
DockerFile
|
||||
.idea
|
||||
|
||||
# Go compiled binaries
|
||||
app/*/api/api
|
||||
app/*/rpc/rpc
|
||||
app/*/mq/mq
|
||||
Generated
+8
-8
@@ -1,8 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
|
||||
Generated
+7
-7
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/st-1-example.iml" filepath="$PROJECT_DIR$/.idea/st-1-example.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/st-1-example.iml" filepath="$PROJECT_DIR$/.idea/st-1-example.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+8
-8
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+5
-5
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+200
-200
@@ -1,200 +1,200 @@
|
||||
import { search } from "@inquirer/prompts";
|
||||
import { execa } from "execa";
|
||||
import Fuse from "fuse.js";
|
||||
import { glob } from "glob";
|
||||
import { task } from "hereby";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: process.argv.slice(3),
|
||||
options: {
|
||||
server: { type: "string", short: "s", multiple: true }, // 服务名称
|
||||
type: { type: "string", short: "t" }, // 生成类型:api, rpc, docker
|
||||
imageName: { type: "string", short: "i" }, // 镜像名字
|
||||
}
|
||||
})
|
||||
|
||||
const Paths = {
|
||||
root: __dirname,
|
||||
desc: path.join(__dirname, "desc"),
|
||||
app: path.join(__dirname, "app"),
|
||||
getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)),
|
||||
getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName),
|
||||
pathlistToChoices: (filePaths) => filePaths.map(filePath => ({
|
||||
title: Paths.getServiceName(filePath),
|
||||
value: filePath,
|
||||
})),
|
||||
getDescFiles: async (pattern) => {
|
||||
return await glob(pattern, { cwd: Paths.desc, absolute: true });
|
||||
},
|
||||
getAllApi: async () => {
|
||||
const apiPattern = "api/*.api";
|
||||
return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern));
|
||||
},
|
||||
getAllProto: async () => {
|
||||
const protoPattern = "rpc/*.proto";
|
||||
return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern));
|
||||
},
|
||||
getAllservice: async () => {
|
||||
let all = [];
|
||||
const services = await fs.readdir(Paths.app);
|
||||
for (const service of services) {
|
||||
const servicePath = path.join(Paths.app, service);
|
||||
const svcTypes = await fs.readdir(servicePath);
|
||||
svcTypes.map(svcType => all.push({
|
||||
title: `${service} - ${svcType}`,
|
||||
value: path.join(servicePath, svcType, svcType !== "rpc" ? `${service}.go` : "pb.go"),
|
||||
}));
|
||||
}
|
||||
return all;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const Generators = {
|
||||
async api(apiFile) {
|
||||
const serviceName = Paths.getServiceName(apiFile);
|
||||
const outputDir = path.join(Paths.getOutputDir(serviceName), 'api');
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
await run('goctl', [
|
||||
'api', 'go',
|
||||
'--api', apiFile,
|
||||
'--dir', outputDir,
|
||||
'--style', 'goZero'
|
||||
]);
|
||||
},
|
||||
async rpc(protoFile) {
|
||||
const serviceName = Paths.getServiceName(protoFile);
|
||||
const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc');
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
await run('goctl', [
|
||||
'rpc', 'protoc', protoFile,
|
||||
`--proto_path=${path.join(Paths.desc, "rpc",)}`,
|
||||
`--go_out=${outputDir}`,
|
||||
`--go-grpc_out=${outputDir}`,
|
||||
`--zrpc_out=${outputDir}`,
|
||||
'--style=goZero',
|
||||
]);
|
||||
},
|
||||
async docker(servicePath) {
|
||||
const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true });
|
||||
if (dockerFiles.length !== 0) {
|
||||
fs.rm(dockerFiles[0], { force: true });
|
||||
}
|
||||
await run('goctl', [
|
||||
"docker", "--go", path.relative(__dirname, servicePath)
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const GenerateConfig = {
|
||||
api: {
|
||||
getChoices: () => Paths.getAllApi(),
|
||||
prompt: "Select an API description file",
|
||||
generate: (path) => Generators.api(path),
|
||||
},
|
||||
rpc: {
|
||||
getChoices: () => Paths.getAllProto(),
|
||||
prompt: "Select a proto file",
|
||||
generate: (path) => Generators.rpc(path),
|
||||
},
|
||||
docker: {
|
||||
getChoices: () => Paths.getAllservice(),
|
||||
prompt: "Select a service to generate Dockerfile",
|
||||
generate: (path) => Generators.docker(path),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
async function run(cmd, args, opts = {}) {
|
||||
console.log(`>> ${cmd} ${args.join(' ')}`);
|
||||
return execa(cmd, args, {
|
||||
stdio: 'inherit',
|
||||
...opts
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function searchSelector(chooses, message) {
|
||||
const fuse = new Fuse(chooses, {
|
||||
keys: ['title'],
|
||||
threshold: 0.4,
|
||||
})
|
||||
return search({
|
||||
message,
|
||||
source: async (term) => {
|
||||
if (!term) {
|
||||
return chooses.map(s => ({ name: s.title, value: s.value }));
|
||||
}
|
||||
const result = fuse.search(term);
|
||||
return result.map(s => ({ name: s.item.title, value: s.item.value }));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async function generateHandle() {
|
||||
const type = values.type;
|
||||
if (!type || !GenerateConfig[type]) {
|
||||
console.error("Please specify valid -t <api|rpc|docker>");
|
||||
return;
|
||||
}
|
||||
const config = GenerateConfig[type];
|
||||
|
||||
const input = values.server
|
||||
? (Array.isArray(values.server) ? values.server[0] : values.server)
|
||||
: await searchSelector(await config.getChoices(), config.prompt);
|
||||
|
||||
await config.generate(input);
|
||||
}
|
||||
|
||||
async function buildImage(imageName) {
|
||||
await run("docker", ["build", "-t", imageName, "."])
|
||||
}
|
||||
|
||||
|
||||
export const init = task({
|
||||
name: "init",
|
||||
desc: "initialize the project",
|
||||
run: async () => {
|
||||
await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export const tidy = task({
|
||||
name: "tidy",
|
||||
desc: "tidy go.mod and go.sum",
|
||||
run: async () => { run("go", ["mod", "tidy"]) },
|
||||
})
|
||||
|
||||
|
||||
export const build = task({
|
||||
name: "build",
|
||||
desc: "build docker image",
|
||||
run: async () => {
|
||||
if (values.imageName) {
|
||||
buildImage(values.imageName);
|
||||
} else {
|
||||
console.error("Please specify image name with -i <imageName>");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export const gen = task({
|
||||
name: "gen",
|
||||
desc: "generate API/RPC service code",
|
||||
run: generateHandle,
|
||||
});
|
||||
|
||||
|
||||
import { search } from "@inquirer/prompts";
|
||||
import { execa } from "execa";
|
||||
import Fuse from "fuse.js";
|
||||
import { glob } from "glob";
|
||||
import { task } from "hereby";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: process.argv.slice(3),
|
||||
options: {
|
||||
server: { type: "string", short: "s", multiple: true }, // 服务名称
|
||||
type: { type: "string", short: "t" }, // 生成类型:api, rpc, docker
|
||||
imageName: { type: "string", short: "i" }, // 镜像名字
|
||||
}
|
||||
})
|
||||
|
||||
const Paths = {
|
||||
root: __dirname,
|
||||
desc: path.join(__dirname, "desc"),
|
||||
app: path.join(__dirname, "app"),
|
||||
getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)),
|
||||
getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName),
|
||||
pathlistToChoices: (filePaths) => filePaths.map(filePath => ({
|
||||
title: Paths.getServiceName(filePath),
|
||||
value: filePath,
|
||||
})),
|
||||
getDescFiles: async (pattern) => {
|
||||
return await glob(pattern, { cwd: Paths.desc, absolute: true });
|
||||
},
|
||||
getAllApi: async () => {
|
||||
const apiPattern = "api/*.api";
|
||||
return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern));
|
||||
},
|
||||
getAllProto: async () => {
|
||||
const protoPattern = "rpc/*.proto";
|
||||
return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern));
|
||||
},
|
||||
getAllservice: async () => {
|
||||
let all = [];
|
||||
const services = await fs.readdir(Paths.app);
|
||||
for (const service of services) {
|
||||
const servicePath = path.join(Paths.app, service);
|
||||
const svcTypes = await fs.readdir(servicePath);
|
||||
svcTypes.map(svcType => all.push({
|
||||
title: `${service} - ${svcType}`,
|
||||
value: path.join(servicePath, svcType, svcType !== "rpc" ? `${service}.go` : "pb.go"),
|
||||
}));
|
||||
}
|
||||
return all;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const Generators = {
|
||||
async api(apiFile) {
|
||||
const serviceName = Paths.getServiceName(apiFile);
|
||||
const outputDir = path.join(Paths.getOutputDir(serviceName), 'api');
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
await run('goctl', [
|
||||
'api', 'go',
|
||||
'--api', apiFile,
|
||||
'--dir', outputDir,
|
||||
'--style', 'goZero'
|
||||
]);
|
||||
},
|
||||
async rpc(protoFile) {
|
||||
const serviceName = Paths.getServiceName(protoFile);
|
||||
const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc');
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
await run('goctl', [
|
||||
'rpc', 'protoc', protoFile,
|
||||
`--proto_path=${path.join(Paths.desc, "rpc",)}`,
|
||||
`--go_out=${outputDir}`,
|
||||
`--go-grpc_out=${outputDir}`,
|
||||
`--zrpc_out=${outputDir}`,
|
||||
'--style=goZero',
|
||||
]);
|
||||
},
|
||||
async docker(servicePath) {
|
||||
const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true });
|
||||
if (dockerFiles.length !== 0) {
|
||||
fs.rm(dockerFiles[0], { force: true });
|
||||
}
|
||||
await run('goctl', [
|
||||
"docker", "--go", path.relative(__dirname, servicePath)
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const GenerateConfig = {
|
||||
api: {
|
||||
getChoices: () => Paths.getAllApi(),
|
||||
prompt: "Select an API description file",
|
||||
generate: (path) => Generators.api(path),
|
||||
},
|
||||
rpc: {
|
||||
getChoices: () => Paths.getAllProto(),
|
||||
prompt: "Select a proto file",
|
||||
generate: (path) => Generators.rpc(path),
|
||||
},
|
||||
docker: {
|
||||
getChoices: () => Paths.getAllservice(),
|
||||
prompt: "Select a service to generate Dockerfile",
|
||||
generate: (path) => Generators.docker(path),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
async function run(cmd, args, opts = {}) {
|
||||
console.log(`>> ${cmd} ${args.join(' ')}`);
|
||||
return execa(cmd, args, {
|
||||
stdio: 'inherit',
|
||||
...opts
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function searchSelector(chooses, message) {
|
||||
const fuse = new Fuse(chooses, {
|
||||
keys: ['title'],
|
||||
threshold: 0.4,
|
||||
})
|
||||
return search({
|
||||
message,
|
||||
source: async (term) => {
|
||||
if (!term) {
|
||||
return chooses.map(s => ({ name: s.title, value: s.value }));
|
||||
}
|
||||
const result = fuse.search(term);
|
||||
return result.map(s => ({ name: s.item.title, value: s.item.value }));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async function generateHandle() {
|
||||
const type = values.type;
|
||||
if (!type || !GenerateConfig[type]) {
|
||||
console.error("Please specify valid -t <api|rpc|docker>");
|
||||
return;
|
||||
}
|
||||
const config = GenerateConfig[type];
|
||||
|
||||
const input = values.server
|
||||
? (Array.isArray(values.server) ? values.server[0] : values.server)
|
||||
: await searchSelector(await config.getChoices(), config.prompt);
|
||||
|
||||
await config.generate(input);
|
||||
}
|
||||
|
||||
async function buildImage(imageName) {
|
||||
await run("docker", ["build", "-t", imageName, "."])
|
||||
}
|
||||
|
||||
|
||||
export const init = task({
|
||||
name: "init",
|
||||
desc: "initialize the project",
|
||||
run: async () => {
|
||||
await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export const tidy = task({
|
||||
name: "tidy",
|
||||
desc: "tidy go.mod and go.sum",
|
||||
run: async () => { run("go", ["mod", "tidy"]) },
|
||||
})
|
||||
|
||||
|
||||
export const build = task({
|
||||
name: "build",
|
||||
desc: "build docker image",
|
||||
run: async () => {
|
||||
if (values.imageName) {
|
||||
buildImage(values.imageName);
|
||||
} else {
|
||||
console.error("Please specify image name with -i <imageName>");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export const gen = task({
|
||||
name: "gen",
|
||||
desc: "generate API/RPC service code",
|
||||
run: generateHandle,
|
||||
});
|
||||
|
||||
|
||||
|
||||
+233
-233
@@ -1,233 +1,233 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc
|
||||
spec:
|
||||
replicas: 3
|
||||
revisionHistoryLimit: 5
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-rpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-rpc
|
||||
spec:
|
||||
serviceAccountName: find-endpoints
|
||||
initContainers: # 等待数据库就绪的 Init Container 不影响资源使用但是影响调度策略(也可以忽略不计)
|
||||
- name: wait-for-db
|
||||
image: busybox:1.36
|
||||
command:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'until nc -z -v -w5 user-db-rw 5432; do echo "Waiting for database..."; sleep 2; done;',
|
||||
]
|
||||
containers:
|
||||
- name: user-rpc
|
||||
image: user-rpc:v1
|
||||
ports:
|
||||
- containerPort: 9001
|
||||
- containerPort: 4001
|
||||
env:
|
||||
- name: DB_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: user-db-app
|
||||
key: uri
|
||||
- name: REDIS_HOST
|
||||
value: "user-redis-sentinel-sentinel.juwan:26379"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: user-redis
|
||||
key: password
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 9001
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 9001
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1024Mi
|
||||
volumeMounts:
|
||||
- name: timezone
|
||||
mountPath: /etc/localtime
|
||||
volumes:
|
||||
- name: timezone
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-rpc-svc
|
||||
namespace: juwan
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "4001"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
ports:
|
||||
- name: rpc
|
||||
port: 9001
|
||||
targetPort: 9001
|
||||
- name: metrics
|
||||
port: 4001
|
||||
targetPort: 4001
|
||||
selector:
|
||||
app: user-rpc
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: user-rpc-hpa-c
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc-hpa-c
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: user-rpc
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: user-rpc-hpa-m
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc-hpa-m
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: user-rpc
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
---
|
||||
# Redis 主从复制
|
||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||
kind: RedisReplication
|
||||
metadata:
|
||||
name: user-redis
|
||||
namespace: juwan
|
||||
spec:
|
||||
clusterSize: 3
|
||||
kubernetesConfig:
|
||||
image: quay.io/opstree/redis:v7.0.12
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
redisSecret:
|
||||
name: user-redis
|
||||
key: password
|
||||
|
||||
redisExporter:
|
||||
enabled: true
|
||||
image: quay.io/opstree/redis-exporter:latest
|
||||
imagePullPolicy: Always
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
storage:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
|
||||
---
|
||||
# Sentinel 监控
|
||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||
kind: RedisSentinel
|
||||
metadata:
|
||||
name: user-redis-sentinel
|
||||
namespace: juwan
|
||||
spec:
|
||||
clusterSize: 3
|
||||
kubernetesConfig:
|
||||
image: quay.io/opstree/redis-sentinel:v7.0.12
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
redisSentinelConfig:
|
||||
redisReplicationName: user-redis
|
||||
masterGroupName: mymaster
|
||||
redisPort: "6379"
|
||||
quorum: "2"
|
||||
downAfterMilliseconds: "5000"
|
||||
failoverTimeout: "10000"
|
||||
parallelSyncs: "1"
|
||||
|
||||
---
|
||||
# PostgreSQL 集群
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
namespace: juwan
|
||||
name: user-db
|
||||
spec:
|
||||
instances: 3
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
||||
endpointURL: https://cn-nb1.rains3.com
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: rc-creds
|
||||
key: SOucqRaJr4OyfcIu
|
||||
secretAccessKey:
|
||||
name: rc-creds
|
||||
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
||||
wal:
|
||||
compression: gzip
|
||||
storage:
|
||||
size: 1Gi
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc
|
||||
spec:
|
||||
replicas: 3
|
||||
revisionHistoryLimit: 5
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-rpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-rpc
|
||||
spec:
|
||||
serviceAccountName: find-endpoints
|
||||
initContainers: # 等待数据库就绪的 Init Container 不影响资源使用但是影响调度策略(也可以忽略不计)
|
||||
- name: wait-for-db
|
||||
image: busybox:1.36
|
||||
command:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'until nc -z -v -w5 user-db-rw 5432; do echo "Waiting for database..."; sleep 2; done;',
|
||||
]
|
||||
containers:
|
||||
- name: user-rpc
|
||||
image: user-rpc:v1
|
||||
ports:
|
||||
- containerPort: 9001
|
||||
- containerPort: 4001
|
||||
env:
|
||||
- name: DB_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: user-db-app
|
||||
key: uri
|
||||
- name: REDIS_HOST
|
||||
value: "user-redis-sentinel-sentinel.juwan:26379"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: user-redis
|
||||
key: password
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 9001
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 9001
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1024Mi
|
||||
volumeMounts:
|
||||
- name: timezone
|
||||
mountPath: /etc/localtime
|
||||
volumes:
|
||||
- name: timezone
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-rpc-svc
|
||||
namespace: juwan
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "4001"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
ports:
|
||||
- name: rpc
|
||||
port: 9001
|
||||
targetPort: 9001
|
||||
- name: metrics
|
||||
port: 4001
|
||||
targetPort: 4001
|
||||
selector:
|
||||
app: user-rpc
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: user-rpc-hpa-c
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc-hpa-c
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: user-rpc
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: user-rpc-hpa-m
|
||||
namespace: juwan
|
||||
labels:
|
||||
app: user-rpc-hpa-m
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: user-rpc
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
---
|
||||
# Redis 主从复制
|
||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||
kind: RedisReplication
|
||||
metadata:
|
||||
name: user-redis
|
||||
namespace: juwan
|
||||
spec:
|
||||
clusterSize: 3
|
||||
kubernetesConfig:
|
||||
image: quay.io/opstree/redis:v7.0.12
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
redisSecret:
|
||||
name: user-redis
|
||||
key: password
|
||||
|
||||
redisExporter:
|
||||
enabled: true
|
||||
image: quay.io/opstree/redis-exporter:latest
|
||||
imagePullPolicy: Always
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
storage:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
|
||||
---
|
||||
# Sentinel 监控
|
||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||
kind: RedisSentinel
|
||||
metadata:
|
||||
name: user-redis-sentinel
|
||||
namespace: juwan
|
||||
spec:
|
||||
clusterSize: 3
|
||||
kubernetesConfig:
|
||||
image: quay.io/opstree/redis-sentinel:v7.0.12
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
podSecurityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
redisSentinelConfig:
|
||||
redisReplicationName: user-redis
|
||||
masterGroupName: mymaster
|
||||
redisPort: "6379"
|
||||
quorum: "2"
|
||||
downAfterMilliseconds: "5000"
|
||||
failoverTimeout: "10000"
|
||||
parallelSyncs: "1"
|
||||
|
||||
---
|
||||
# PostgreSQL 集群
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
namespace: juwan
|
||||
name: user-db
|
||||
spec:
|
||||
instances: 3
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
||||
endpointURL: https://cn-nb1.rains3.com
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: rc-creds
|
||||
key: SOucqRaJr4OyfcIu
|
||||
secretAccessKey:
|
||||
name: rc-creds
|
||||
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
||||
wal:
|
||||
compression: gzip
|
||||
storage:
|
||||
size: 1Gi
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
|
||||
+260
-260
@@ -1,260 +1,260 @@
|
||||
# Converter - 通用结构体转换工具
|
||||
|
||||
利用 Go 反射机制,实现自动的 model 到 protobuf 结构体转换。
|
||||
|
||||
## 功能特性
|
||||
|
||||
✅ **自动字段映射** - 自动匹配同名字段并赋值
|
||||
✅ **智能类型转换** - 自动处理常见类型转换
|
||||
✅ **通用设计** - 支持任何 model 和 pb 结构体,无需手动编写
|
||||
✅ **灵活扩展** - 支持自定义类型转换规则
|
||||
|
||||
## 支持的类型转换
|
||||
|
||||
| 源类型 | 目标类型 | 说明 |
|
||||
|-------|---------|------|
|
||||
| `time.Time` | `int64` | 转换为 Unix 时间戳 |
|
||||
| `sql.NullTime` | `int64` | 有效时自动转换,无效则为 0 |
|
||||
| `sql.NullTime` | `time.Time` | 有效时自动转换,无效则为零值 |
|
||||
| `sql.NullInt64` | `int64` | 有效时自动转换,无效则为 0 |
|
||||
| `sql.NullString` | `string` | 有效时自动转换,无效则为空字符串 |
|
||||
| `sql.NullBool` | `bool` | 有效时自动转换,无效则为 false |
|
||||
| `int` | `int64` | 自动转换 |
|
||||
| `int64` | `int` | 自动转换 |
|
||||
| 相同类型 | 相同类型 | 直接复制 |
|
||||
|
||||
## 核心函数
|
||||
|
||||
### 1. StructToStruct - 单个结构体转换
|
||||
|
||||
```go
|
||||
func StructToStruct(src, dst interface{}) error
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `src` - 源结构体(可以是指针或值类型)
|
||||
- `dst` - 目标结构体(必须是指针)
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
import "app/common/converter"
|
||||
|
||||
// 单个 models 转 pb
|
||||
user, _ := m.FindOne(ctx, userId)
|
||||
pbUser := &pb.Users{}
|
||||
converter.StructToStruct(user, pbUser)
|
||||
|
||||
// 或直接点对点转换
|
||||
pbUser := &pb.Users{}
|
||||
_ = converter.StructToStruct(user, pbUser)
|
||||
```
|
||||
|
||||
### 2. SliceToSlice - 切片转换
|
||||
|
||||
```go
|
||||
func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `src` - 源切片
|
||||
- `dstSliceType` - 目标切片类型(用于推导元素类型)
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
// 多个 models 转 pb
|
||||
users := []*models.Users{user1, user2, user3}
|
||||
pbUsersIface, _ := converter.SliceToSlice(users, []*pb.Users{})
|
||||
pbUsers := pbUsersIface.([]*pb.Users)
|
||||
```
|
||||
|
||||
### 3. UserModelToPb - Users 专用转换(推荐)
|
||||
|
||||
```go
|
||||
func UserModelToPb(user *models.Users) *pb.Users
|
||||
```
|
||||
|
||||
简化的 Users model 转 pb 的快捷函数。
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
user, _ := m.FindOne(ctx, userId)
|
||||
pbUser := converter.UserModelToPb(user)
|
||||
```
|
||||
|
||||
### 4. UserModelsToPb - Users 批量转换(推荐)
|
||||
|
||||
```go
|
||||
func UserModelsToPb(users []*models.Users) []*pb.Users
|
||||
```
|
||||
|
||||
简化的批量转换快捷函数。
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
users, _ := m.FindAll(ctx)
|
||||
pbUsers := converter.UserModelsToPb(users)
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景 1:在 Logic 层直接转换
|
||||
|
||||
```go
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"app/common/converter"
|
||||
"app/users/rpc/internal/models"
|
||||
"app/users/rpc/pb"
|
||||
)
|
||||
|
||||
type GetUserByIdLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func (l *GetUserByIdLogic) GetUserById(req *pb.GetUserByIdReq) (*pb.Users, error) {
|
||||
// 查询数据库
|
||||
user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 直接转换,无需手动赋值每个字段
|
||||
pbUser := converter.UserModelToPb(user)
|
||||
|
||||
return pbUser, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 2:批量操作
|
||||
|
||||
```go
|
||||
func (l *ListUsersLogic) ListUsers(req *pb.ListUsersReq) (*pb.ListUsersResp, error) {
|
||||
users, err := l.svcCtx.UsersModel.FindAll(l.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 批量转换
|
||||
pbUsers := converter.UserModelsToPb(users)
|
||||
|
||||
return &pb.ListUsersResp{
|
||||
Users: pbUsers,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 3:搜索/过滤结果
|
||||
|
||||
```go
|
||||
func (l *SearchUsersLogic) SearchUsers(req *pb.SearchReq) (*pb.SearchResp, error) {
|
||||
// 搜索数据库
|
||||
results, err := l.svcCtx.UsersModel.SearchByKeyword(l.ctx, req.Keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pbUsers := converter.UserModelsToPb(results)
|
||||
|
||||
return &pb.SearchResp{
|
||||
Results: pbUsers,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 处理特殊字段
|
||||
|
||||
### NULLable 字段
|
||||
|
||||
当源字段是 `sql.NullTime` 或其他 `sql.Null*` 类型时,转换器会自动处理:
|
||||
|
||||
```go
|
||||
// sql.NullTime -> int64(有效情况)
|
||||
user.DeletedAt = sql.NullTime{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
}
|
||||
// 转换后 pb.Users.DeletedAt 会包含 Unix 时间戳
|
||||
|
||||
// sql.NullTime -> int64(无效情况)
|
||||
user.DeletedAt = sql.NullTime{
|
||||
Valid: false,
|
||||
}
|
||||
// 转换后 pb.Users.DeletedAt 为 0
|
||||
```
|
||||
|
||||
### 时间戳字段
|
||||
|
||||
数据库中的 `time.Time` 字段会自动转换为 protobuf 中的 `int64` Unix 时间戳:
|
||||
|
||||
```go
|
||||
// Model
|
||||
type Users struct {
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
DeletedAt sql.NullTime `db:"deleted_at"`
|
||||
}
|
||||
|
||||
// Protobuf
|
||||
type Users struct {
|
||||
CreatedAt int64 // 自动转换为 Unix 时间戳
|
||||
UpdatedAt int64 // 自动转换为 Unix 时间戳
|
||||
DeletedAt int64 // 有效时转换,无效时为 0
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展 - 添加自定义类型转换
|
||||
|
||||
如果需要支持新的类型转换,可以在 `generic.go` 的 `assignValue` 函数中添加:
|
||||
|
||||
```go
|
||||
// 处理自定义类型 MyType -> int32 的转换
|
||||
if srcType == reflect.TypeOf(MyType{}) && dstType.Kind() == reflect.Int32 {
|
||||
mt := srcField.Interface().(MyType)
|
||||
dstField.SetInt(int64(mt.SomeIntField))
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
- 反射操作相对于直接赋值会有性能开销(通常很小)
|
||||
- 如果需要转换大量数据(>10000 条),考虑性能测试
|
||||
- 对于热点代码路径,可以写针对性的转换函数
|
||||
|
||||
## 错误处理
|
||||
|
||||
```go
|
||||
err := converter.StructToStruct(src, dst)
|
||||
if err != nil {
|
||||
log.Printf("转换失败: %v", err)
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
大多数字段级别的转换错误会被忽略(自动跳过),但结构化错误(如 dst 不是指针)会返回。
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 字段名必须完全相同吗?**
|
||||
A: 是的,转换器通过反射按字段名匹配。如果 model 字段名是 `UserId`,pb 字段也必须是 `UserId`。
|
||||
|
||||
**Q: 如果某个字段转换失败怎么办?**
|
||||
A: 单个字段的转换失败会被忽略,继续处理其他字段。确保其他字段正确设置。
|
||||
|
||||
**Q: 能否自定义字段映射规则(比如 `db_name` -> `pbName`)?**
|
||||
A: 当前不支持。如果需要,应该在 protobuf 定义中使用与 model 相同的字段名。
|
||||
|
||||
**Q: 转换速度快吗?**
|
||||
A: 反射会有性能开销,但对于大多数应用场景是可接受的。如果有极端性能要求,可以手写转换函数。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `generic.go` - 通用转换函数核心实现
|
||||
- `user_converter.go` - Users model 专用转换函数(示例)
|
||||
# Converter - 通用结构体转换工具
|
||||
|
||||
利用 Go 反射机制,实现自动的 model 到 protobuf 结构体转换。
|
||||
|
||||
## 功能特性
|
||||
|
||||
✅ **自动字段映射** - 自动匹配同名字段并赋值
|
||||
✅ **智能类型转换** - 自动处理常见类型转换
|
||||
✅ **通用设计** - 支持任何 model 和 pb 结构体,无需手动编写
|
||||
✅ **灵活扩展** - 支持自定义类型转换规则
|
||||
|
||||
## 支持的类型转换
|
||||
|
||||
| 源类型 | 目标类型 | 说明 |
|
||||
|-------|---------|------|
|
||||
| `time.Time` | `int64` | 转换为 Unix 时间戳 |
|
||||
| `sql.NullTime` | `int64` | 有效时自动转换,无效则为 0 |
|
||||
| `sql.NullTime` | `time.Time` | 有效时自动转换,无效则为零值 |
|
||||
| `sql.NullInt64` | `int64` | 有效时自动转换,无效则为 0 |
|
||||
| `sql.NullString` | `string` | 有效时自动转换,无效则为空字符串 |
|
||||
| `sql.NullBool` | `bool` | 有效时自动转换,无效则为 false |
|
||||
| `int` | `int64` | 自动转换 |
|
||||
| `int64` | `int` | 自动转换 |
|
||||
| 相同类型 | 相同类型 | 直接复制 |
|
||||
|
||||
## 核心函数
|
||||
|
||||
### 1. StructToStruct - 单个结构体转换
|
||||
|
||||
```go
|
||||
func StructToStruct(src, dst interface{}) error
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `src` - 源结构体(可以是指针或值类型)
|
||||
- `dst` - 目标结构体(必须是指针)
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
import "app/common/converter"
|
||||
|
||||
// 单个 models 转 pb
|
||||
user, _ := m.FindOne(ctx, userId)
|
||||
pbUser := &pb.Users{}
|
||||
converter.StructToStruct(user, pbUser)
|
||||
|
||||
// 或直接点对点转换
|
||||
pbUser := &pb.Users{}
|
||||
_ = converter.StructToStruct(user, pbUser)
|
||||
```
|
||||
|
||||
### 2. SliceToSlice - 切片转换
|
||||
|
||||
```go
|
||||
func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `src` - 源切片
|
||||
- `dstSliceType` - 目标切片类型(用于推导元素类型)
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
// 多个 models 转 pb
|
||||
users := []*models.Users{user1, user2, user3}
|
||||
pbUsersIface, _ := converter.SliceToSlice(users, []*pb.Users{})
|
||||
pbUsers := pbUsersIface.([]*pb.Users)
|
||||
```
|
||||
|
||||
### 3. UserModelToPb - Users 专用转换(推荐)
|
||||
|
||||
```go
|
||||
func UserModelToPb(user *models.Users) *pb.Users
|
||||
```
|
||||
|
||||
简化的 Users model 转 pb 的快捷函数。
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
user, _ := m.FindOne(ctx, userId)
|
||||
pbUser := converter.UserModelToPb(user)
|
||||
```
|
||||
|
||||
### 4. UserModelsToPb - Users 批量转换(推荐)
|
||||
|
||||
```go
|
||||
func UserModelsToPb(users []*models.Users) []*pb.Users
|
||||
```
|
||||
|
||||
简化的批量转换快捷函数。
|
||||
|
||||
**示例:**
|
||||
|
||||
```go
|
||||
users, _ := m.FindAll(ctx)
|
||||
pbUsers := converter.UserModelsToPb(users)
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景 1:在 Logic 层直接转换
|
||||
|
||||
```go
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"app/common/converter"
|
||||
"app/users/rpc/internal/models"
|
||||
"app/users/rpc/pb"
|
||||
)
|
||||
|
||||
type GetUserByIdLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func (l *GetUserByIdLogic) GetUserById(req *pb.GetUserByIdReq) (*pb.Users, error) {
|
||||
// 查询数据库
|
||||
user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 直接转换,无需手动赋值每个字段
|
||||
pbUser := converter.UserModelToPb(user)
|
||||
|
||||
return pbUser, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 2:批量操作
|
||||
|
||||
```go
|
||||
func (l *ListUsersLogic) ListUsers(req *pb.ListUsersReq) (*pb.ListUsersResp, error) {
|
||||
users, err := l.svcCtx.UsersModel.FindAll(l.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 批量转换
|
||||
pbUsers := converter.UserModelsToPb(users)
|
||||
|
||||
return &pb.ListUsersResp{
|
||||
Users: pbUsers,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 3:搜索/过滤结果
|
||||
|
||||
```go
|
||||
func (l *SearchUsersLogic) SearchUsers(req *pb.SearchReq) (*pb.SearchResp, error) {
|
||||
// 搜索数据库
|
||||
results, err := l.svcCtx.UsersModel.SearchByKeyword(l.ctx, req.Keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pbUsers := converter.UserModelsToPb(results)
|
||||
|
||||
return &pb.SearchResp{
|
||||
Results: pbUsers,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 处理特殊字段
|
||||
|
||||
### NULLable 字段
|
||||
|
||||
当源字段是 `sql.NullTime` 或其他 `sql.Null*` 类型时,转换器会自动处理:
|
||||
|
||||
```go
|
||||
// sql.NullTime -> int64(有效情况)
|
||||
user.DeletedAt = sql.NullTime{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
}
|
||||
// 转换后 pb.Users.DeletedAt 会包含 Unix 时间戳
|
||||
|
||||
// sql.NullTime -> int64(无效情况)
|
||||
user.DeletedAt = sql.NullTime{
|
||||
Valid: false,
|
||||
}
|
||||
// 转换后 pb.Users.DeletedAt 为 0
|
||||
```
|
||||
|
||||
### 时间戳字段
|
||||
|
||||
数据库中的 `time.Time` 字段会自动转换为 protobuf 中的 `int64` Unix 时间戳:
|
||||
|
||||
```go
|
||||
// Model
|
||||
type Users struct {
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
DeletedAt sql.NullTime `db:"deleted_at"`
|
||||
}
|
||||
|
||||
// Protobuf
|
||||
type Users struct {
|
||||
CreatedAt int64 // 自动转换为 Unix 时间戳
|
||||
UpdatedAt int64 // 自动转换为 Unix 时间戳
|
||||
DeletedAt int64 // 有效时转换,无效时为 0
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展 - 添加自定义类型转换
|
||||
|
||||
如果需要支持新的类型转换,可以在 `generic.go` 的 `assignValue` 函数中添加:
|
||||
|
||||
```go
|
||||
// 处理自定义类型 MyType -> int32 的转换
|
||||
if srcType == reflect.TypeOf(MyType{}) && dstType.Kind() == reflect.Int32 {
|
||||
mt := srcField.Interface().(MyType)
|
||||
dstField.SetInt(int64(mt.SomeIntField))
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
- 反射操作相对于直接赋值会有性能开销(通常很小)
|
||||
- 如果需要转换大量数据(>10000 条),考虑性能测试
|
||||
- 对于热点代码路径,可以写针对性的转换函数
|
||||
|
||||
## 错误处理
|
||||
|
||||
```go
|
||||
err := converter.StructToStruct(src, dst)
|
||||
if err != nil {
|
||||
log.Printf("转换失败: %v", err)
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
大多数字段级别的转换错误会被忽略(自动跳过),但结构化错误(如 dst 不是指针)会返回。
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 字段名必须完全相同吗?**
|
||||
A: 是的,转换器通过反射按字段名匹配。如果 model 字段名是 `UserId`,pb 字段也必须是 `UserId`。
|
||||
|
||||
**Q: 如果某个字段转换失败怎么办?**
|
||||
A: 单个字段的转换失败会被忽略,继续处理其他字段。确保其他字段正确设置。
|
||||
|
||||
**Q: 能否自定义字段映射规则(比如 `db_name` -> `pbName`)?**
|
||||
A: 当前不支持。如果需要,应该在 protobuf 定义中使用与 model 相同的字段名。
|
||||
|
||||
**Q: 转换速度快吗?**
|
||||
A: 反射会有性能开销,但对于大多数应用场景是可接受的。如果有极端性能要求,可以手写转换函数。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `generic.go` - 通用转换函数核心实现
|
||||
- `user_converter.go` - Users model 专用转换函数(示例)
|
||||
|
||||
+30
-30
@@ -1,30 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFFTCCAv2gAwIBAgIUXj+1vyqKDhTsubwSmcHY61+YvmQwDQYJKoZIhvcNAQEL
|
||||
BQAwGjEYMBYGA1UEAwwPYXBpLmp1d2FuLmxvY2FsMB4XDTI2MDIyMzExNTYxNloX
|
||||
DTI3MDIyMzExNTYxNlowGjEYMBYGA1UEAwwPYXBpLmp1d2FuLmxvY2FsMIICIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkn7Jw5f0awGFbGL3ZHEPJanZO9Yk
|
||||
JDUklLF3kABiXqawSFpM6pXfKMa4VHE6/MfpREQeX2lkvtBseOf/vhC4DLACui8g
|
||||
yslUObv77xGSXmIwjFcXZzLPQ/gEs2lxikxeoI4Su9qpsUQNzUD10rvWMx0iea8Z
|
||||
47Z4RI6fIlA5xC5N4VfUFQdE/VN670HdiTZ7YAFIg9F/ZJQMH+hPVNSLgY6J0RdU
|
||||
3gqKAkAvmCQZyQKWG1eRqKauw4CIvk6d7N+nOzmwDb6clueFj7Kx4h4IAFHCQthn
|
||||
eXrf21uBCVwVjs64ilnTVwFfklr79euYRHPmRqR5eswbIGpDEFOaf1smu4hrkK9s
|
||||
tQ8YWey8TICymBaXr1hI+WjSVEQFN8xPoVQwiKJRdu7lIosDjbH8V/ooKGMhCHgl
|
||||
5C995L3sKsMyCMkw90viYNy2jUuSNu2X3eK+QJip2D2smfSM2tBsFtiXyEk+WeyY
|
||||
cRDlwB7+6vvVwCHqz0+4lr0HHBEky43m3NgUtZoulfRwv4znGXcMqvxVUm4pwoBf
|
||||
lo7zVuXh+cXrEzzCksQiCBzBM115itb3la8RX8A4bRUs38XG6Bz+Qfr6RQspppV1
|
||||
vNd5mUOyBYNeVfErf59PnFsdMI3kD0UgwpLkkGdSGdzDKykdt7vffNRpV8jOYuuO
|
||||
LxH+2WlebCv1N90CAwEAAaNTMFEwHQYDVR0OBBYEFF80R0EZORGRXrZTVrAfaatK
|
||||
eNi9MB8GA1UdIwQYMBaAFF80R0EZORGRXrZTVrAfaatKeNi9MA8GA1UdEwEB/wQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAHFZUflyNOCJqV+RghOAaVFDc7wqtZJ1
|
||||
d2dpIs28kKd43Nd+xjSZLmSmVhcntQNwqC8AHIuJKKNDmM5BRnzls1ZO+OLc+YcC
|
||||
kXzO2aBrNz8a0S0nYGzgR+CoTPvd61RGGHbqQNvZiroWsC4NaR+7NYPzsORNaN+1
|
||||
p/xqZygOYLOcD5tP5iNlgBugD+nPEHL0cylE0XpoZ059MIITdlvsrdPgHhFn9Nvv
|
||||
McPZp4nzpJvyUmVjkbT7ZbKIJFrOQ6qJ9U2y55F4xuHzvnaAsOGnGx1tyBHtvkA1
|
||||
IIovrku4su3TmMsBs/6ikT8XSR20gcsDq3N2RcFtgU5LONsWvUL9CTp7P7lMlIfg
|
||||
v1RelzXDE2mESlZEbzbFyVVGAoEPZA4t6kgBV4zObxxp4YmimqGWmVs3qQ/A6wbV
|
||||
OO4rLYW7NZeJLLvsGOabVK+jyFCMyB3YOS6nZ9q48SaWCHlFTZveluP5n/8Y5LGc
|
||||
ppjaZbsG2/apCqlown6jKT7hkP84eu3a+HyQ6ZXpCa6P9c9OZ8bVlP8dXi4mRuhU
|
||||
lINwIKA0HbFAzwhyArMkLFWsw26ImusLZH1KUjHabzbfxnDgb9hwIlSGyPrcHcYY
|
||||
lXTlThSXL0ERoqafQTE9tpPFXC+LCneytAKUgM2TZ1KhRlisA9Tb3i0X4y/yJba7
|
||||
T2Eqz8rRnaIe
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFFTCCAv2gAwIBAgIUXj+1vyqKDhTsubwSmcHY61+YvmQwDQYJKoZIhvcNAQEL
|
||||
BQAwGjEYMBYGA1UEAwwPYXBpLmp1d2FuLmxvY2FsMB4XDTI2MDIyMzExNTYxNloX
|
||||
DTI3MDIyMzExNTYxNlowGjEYMBYGA1UEAwwPYXBpLmp1d2FuLmxvY2FsMIICIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkn7Jw5f0awGFbGL3ZHEPJanZO9Yk
|
||||
JDUklLF3kABiXqawSFpM6pXfKMa4VHE6/MfpREQeX2lkvtBseOf/vhC4DLACui8g
|
||||
yslUObv77xGSXmIwjFcXZzLPQ/gEs2lxikxeoI4Su9qpsUQNzUD10rvWMx0iea8Z
|
||||
47Z4RI6fIlA5xC5N4VfUFQdE/VN670HdiTZ7YAFIg9F/ZJQMH+hPVNSLgY6J0RdU
|
||||
3gqKAkAvmCQZyQKWG1eRqKauw4CIvk6d7N+nOzmwDb6clueFj7Kx4h4IAFHCQthn
|
||||
eXrf21uBCVwVjs64ilnTVwFfklr79euYRHPmRqR5eswbIGpDEFOaf1smu4hrkK9s
|
||||
tQ8YWey8TICymBaXr1hI+WjSVEQFN8xPoVQwiKJRdu7lIosDjbH8V/ooKGMhCHgl
|
||||
5C995L3sKsMyCMkw90viYNy2jUuSNu2X3eK+QJip2D2smfSM2tBsFtiXyEk+WeyY
|
||||
cRDlwB7+6vvVwCHqz0+4lr0HHBEky43m3NgUtZoulfRwv4znGXcMqvxVUm4pwoBf
|
||||
lo7zVuXh+cXrEzzCksQiCBzBM115itb3la8RX8A4bRUs38XG6Bz+Qfr6RQspppV1
|
||||
vNd5mUOyBYNeVfErf59PnFsdMI3kD0UgwpLkkGdSGdzDKykdt7vffNRpV8jOYuuO
|
||||
LxH+2WlebCv1N90CAwEAAaNTMFEwHQYDVR0OBBYEFF80R0EZORGRXrZTVrAfaatK
|
||||
eNi9MB8GA1UdIwQYMBaAFF80R0EZORGRXrZTVrAfaatKeNi9MA8GA1UdEwEB/wQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAHFZUflyNOCJqV+RghOAaVFDc7wqtZJ1
|
||||
d2dpIs28kKd43Nd+xjSZLmSmVhcntQNwqC8AHIuJKKNDmM5BRnzls1ZO+OLc+YcC
|
||||
kXzO2aBrNz8a0S0nYGzgR+CoTPvd61RGGHbqQNvZiroWsC4NaR+7NYPzsORNaN+1
|
||||
p/xqZygOYLOcD5tP5iNlgBugD+nPEHL0cylE0XpoZ059MIITdlvsrdPgHhFn9Nvv
|
||||
McPZp4nzpJvyUmVjkbT7ZbKIJFrOQ6qJ9U2y55F4xuHzvnaAsOGnGx1tyBHtvkA1
|
||||
IIovrku4su3TmMsBs/6ikT8XSR20gcsDq3N2RcFtgU5LONsWvUL9CTp7P7lMlIfg
|
||||
v1RelzXDE2mESlZEbzbFyVVGAoEPZA4t6kgBV4zObxxp4YmimqGWmVs3qQ/A6wbV
|
||||
OO4rLYW7NZeJLLvsGOabVK+jyFCMyB3YOS6nZ9q48SaWCHlFTZveluP5n/8Y5LGc
|
||||
ppjaZbsG2/apCqlown6jKT7hkP84eu3a+HyQ6ZXpCa6P9c9OZ8bVlP8dXi4mRuhU
|
||||
lINwIKA0HbFAzwhyArMkLFWsw26ImusLZH1KUjHabzbfxnDgb9hwIlSGyPrcHcYY
|
||||
lXTlThSXL0ERoqafQTE9tpPFXC+LCneytAKUgM2TZ1KhRlisA9Tb3i0X4y/yJba7
|
||||
T2Eqz8rRnaIe
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
+52
-52
@@ -1,52 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCSfsnDl/RrAYVs
|
||||
YvdkcQ8lqdk71iQkNSSUsXeQAGJeprBIWkzqld8oxrhUcTr8x+lERB5faWS+0Gx4
|
||||
5/++ELgMsAK6LyDKyVQ5u/vvEZJeYjCMVxdnMs9D+ASzaXGKTF6gjhK72qmxRA3N
|
||||
QPXSu9YzHSJ5rxnjtnhEjp8iUDnELk3hV9QVB0T9U3rvQd2JNntgAUiD0X9klAwf
|
||||
6E9U1IuBjonRF1TeCooCQC+YJBnJApYbV5Gopq7DgIi+Tp3s36c7ObANvpyW54WP
|
||||
srHiHggAUcJC2Gd5et/bW4EJXBWOzriKWdNXAV+SWvv165hEc+ZGpHl6zBsgakMQ
|
||||
U5p/Wya7iGuQr2y1DxhZ7LxMgLKYFpevWEj5aNJURAU3zE+hVDCIolF27uUiiwON
|
||||
sfxX+igoYyEIeCXkL33kvewqwzIIyTD3S+Jg3LaNS5I27Zfd4r5AmKnYPayZ9Iza
|
||||
0GwW2JfIST5Z7JhxEOXAHv7q+9XAIerPT7iWvQccESTLjebc2BS1mi6V9HC/jOcZ
|
||||
dwyq/FVSbinCgF+WjvNW5eH5xesTPMKSxCIIHMEzXXmK1veVrxFfwDhtFSzfxcbo
|
||||
HP5B+vpFCymmlXW813mZQ7IFg15V8St/n0+cWx0wjeQPRSDCkuSQZ1IZ3MMrKR23
|
||||
u9981GlXyM5i644vEf7ZaV5sK/U33QIDAQABAoICAA34ohDxm8mdxEYFPT9ayf1H
|
||||
UNS0VE+QsuusbjDxXHBW+N55oDbKMtV+eENzZhMIFM7iKTxjvow1L/cq9xi/GvJ4
|
||||
0dXEW14Dq/DypPEUra8rMaKcxrpcnehHTdl3f7DXHjo1OoOoc8EYcrGF1bvylpfa
|
||||
2jgdMzykoR02teYNnSjA2sQYPn1/6zw2uzV4xGJK7CLIlIwfzYS/2tUrMG+wcpqZ
|
||||
R7sFfN5NRoK28OMTZFMnmD3E0Psy5F14U3JE6KpX3SjYlFoHOQNqUrJU8kKUpyIy
|
||||
qfJ6lYnAJnvS4wBLxDGRtQda0D1Ov/jjDP8T6Dp1DDvmAUDtGNQzVjCHLKejP5MD
|
||||
ltUjTDiFqSXzcRmEV2Jq8y/DjqWieM3BGGl77W6W8eksYqLSo2Ik4fJLqoTm5TSw
|
||||
QY6d8/9gZAP+0E64MWnu0cxpMEXikPPrcjhcTFASxNBoxVhxKseRt+tgkgP4krPu
|
||||
hG2WsWY7n5B0iuO5Dxi0yttT5LfpKcrmRlQXqs0Jdn6nxA7us62WgegxBCXPHCpE
|
||||
rMHlsbrmJECkvnQ11P7eRnpD56b5uD7Kg4uMcUVdY+EKESjm2SwK0FrSBJRvQ/mg
|
||||
JKC7rf2tx7XB3tiKPrmygtLzwyU18+drCMI7fpcrf7wgwyuSdH3klkqnjF/xchQ9
|
||||
RkT3ZDR6mpxhv/ytoXrhAoIBAQDISEKgj/Z+2bNLhryvRfQACzvLHVp59oyI5Xa6
|
||||
MxLIVtozpq05wJxUgY4iPVXLx1Vm9/osHhXQtsFwMQTG2RI0tcz4N7YXrH4acmlm
|
||||
ErdoORtcRX15mEVAl7Mwac4LVyllOZ1D9woKboHDmlBO2L8FUXy8RiLdUT0jgK7k
|
||||
ShWb35twbqwQDLezLEiMnxKFCarVFVBTxVn2bhRA5jcPU+9S2oK9Qx/Mei7QlKKE
|
||||
uTXLOTtNGSvY/7h0dExzS8nXwRDvsVCrWCT5pca1KfmR/JPOPbM6I/vwziSIqMNk
|
||||
GfZWe5IlsQRtyZ49DpA+eDQxzMhxjZhWQ6JR5iQFWUCtXKIhAoIBAQC7P+pPEf9l
|
||||
KOhdPJu6p9NPQu6+hMTi5rTyCDsH5VggvoLKDZTJ1BSqWtW1K09UafRWP5vOxm7u
|
||||
fBYcnqu0W5RSUuoQTZiu03ZhYLBV5vbR+Icx0Hc2BDl8eEIyevyqsmm8+w5i28Ar
|
||||
knep4sP7/n+q2EAK1B2ZlNXXz6f47CMQMkVvZp3FR0F7R6yJoS32tTL5wDOxuOFG
|
||||
LYQOG/yI5JWXwBXov83zrpc+C7kl4gV3xAOk7fZ0exoRdmuvdLS96Ans85L7J8RW
|
||||
ELSfhmGahM+SQ1oJMcV/wYqF2qeLL2F8DZbjR5izLZgkNz4a/VMl/A6YHtuTBXAY
|
||||
+5PXXUOX+9Y9AoIBACI+II4dLxLPG9WM6tO4zRf407dNhHuXyL1bJip9svdnyhTM
|
||||
qY9XPCNCp095VyLpKNPbD/3dAvPVW0tYRi3NTUyPzMSfmdWAW2sgJp8aEhuSr/fd
|
||||
ta9Fdomtpihf3qeXtm8lI5tMMH5KGIud5Z8ldbtuDDqQb0ORsTdRuBU2CW3GFGhr
|
||||
s6Vm1z2eE6VfSSZP2dJmu34nHtOATJwwADfxrNhonbPINzaZqUlmMEcq92SQm2/6
|
||||
HsISLrJSdAO+cHsf+kpQ8a7p+iBo1ImC7LWmDotTh0IohtnMFPj8ibOisLhmlj01
|
||||
f8FZmGFuDQFxQdNF5PttLx+InscL5xq3ANTjIqECggEADpdtd9nsMALfEJzveb0o
|
||||
P0308s2/1fqqcQ3pI7Vgh7Sw1nP2ez/WmGvZqXOFjAtxqeLtDlDyRg1PX82Rjc1x
|
||||
InUpnjmdw0nhOLdjJl6IL1aRmnUnRQNRQ3zPk8V3uQmMKdjahyOetwaD4q40HYf4
|
||||
hOSzIOTkpZoui9G3wjMMjG+Ob57sfnoOBUBRlqwDu+zk2wd6P8grbd+QIdVWeYhu
|
||||
i9PBIVEJCIs7Z+9b7zLMwEd7DTgp82vAXUoAHD0Y9I+HbnqQope3ugk1OhUrt/HP
|
||||
hxNOidbiEBGR7NpcIgGAND2O24kxwgy0hWX0pf/FofkhXgNRkwRidt/r5mVzJf3O
|
||||
9QKCAQAcPXczJY1gynUA8uD/1ODmjpDjWAk0EKBEWY5X2oULv2+xGMNTbT8pwE3f
|
||||
1rszdtF3ckDPoBn7XS9OJwHnVHfXZNJHBtq9utLu0ccE+29HRG0pLCzATsvtoBWi
|
||||
MEwZ2mPqhVpktfqEnL27l/QHkP7dNOyh+halVCHMfy1aNMY6hsKrOcmVmYHVARX0
|
||||
Np2sG9zQszE0+t2mf8Pfd7cEvVuSTIfYZnW+77+PaVkICXXX0rrvwXVh/DVXwmWH
|
||||
kYbDIdiNs9NEFwCmIvzLVsCp0qGUuq9txYo/ML5PMzJhN6X3U+rV42GkkT7KxwH2
|
||||
Izss0+mp4ijKEFQuCGCkxjFmxUEq
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCSfsnDl/RrAYVs
|
||||
YvdkcQ8lqdk71iQkNSSUsXeQAGJeprBIWkzqld8oxrhUcTr8x+lERB5faWS+0Gx4
|
||||
5/++ELgMsAK6LyDKyVQ5u/vvEZJeYjCMVxdnMs9D+ASzaXGKTF6gjhK72qmxRA3N
|
||||
QPXSu9YzHSJ5rxnjtnhEjp8iUDnELk3hV9QVB0T9U3rvQd2JNntgAUiD0X9klAwf
|
||||
6E9U1IuBjonRF1TeCooCQC+YJBnJApYbV5Gopq7DgIi+Tp3s36c7ObANvpyW54WP
|
||||
srHiHggAUcJC2Gd5et/bW4EJXBWOzriKWdNXAV+SWvv165hEc+ZGpHl6zBsgakMQ
|
||||
U5p/Wya7iGuQr2y1DxhZ7LxMgLKYFpevWEj5aNJURAU3zE+hVDCIolF27uUiiwON
|
||||
sfxX+igoYyEIeCXkL33kvewqwzIIyTD3S+Jg3LaNS5I27Zfd4r5AmKnYPayZ9Iza
|
||||
0GwW2JfIST5Z7JhxEOXAHv7q+9XAIerPT7iWvQccESTLjebc2BS1mi6V9HC/jOcZ
|
||||
dwyq/FVSbinCgF+WjvNW5eH5xesTPMKSxCIIHMEzXXmK1veVrxFfwDhtFSzfxcbo
|
||||
HP5B+vpFCymmlXW813mZQ7IFg15V8St/n0+cWx0wjeQPRSDCkuSQZ1IZ3MMrKR23
|
||||
u9981GlXyM5i644vEf7ZaV5sK/U33QIDAQABAoICAA34ohDxm8mdxEYFPT9ayf1H
|
||||
UNS0VE+QsuusbjDxXHBW+N55oDbKMtV+eENzZhMIFM7iKTxjvow1L/cq9xi/GvJ4
|
||||
0dXEW14Dq/DypPEUra8rMaKcxrpcnehHTdl3f7DXHjo1OoOoc8EYcrGF1bvylpfa
|
||||
2jgdMzykoR02teYNnSjA2sQYPn1/6zw2uzV4xGJK7CLIlIwfzYS/2tUrMG+wcpqZ
|
||||
R7sFfN5NRoK28OMTZFMnmD3E0Psy5F14U3JE6KpX3SjYlFoHOQNqUrJU8kKUpyIy
|
||||
qfJ6lYnAJnvS4wBLxDGRtQda0D1Ov/jjDP8T6Dp1DDvmAUDtGNQzVjCHLKejP5MD
|
||||
ltUjTDiFqSXzcRmEV2Jq8y/DjqWieM3BGGl77W6W8eksYqLSo2Ik4fJLqoTm5TSw
|
||||
QY6d8/9gZAP+0E64MWnu0cxpMEXikPPrcjhcTFASxNBoxVhxKseRt+tgkgP4krPu
|
||||
hG2WsWY7n5B0iuO5Dxi0yttT5LfpKcrmRlQXqs0Jdn6nxA7us62WgegxBCXPHCpE
|
||||
rMHlsbrmJECkvnQ11P7eRnpD56b5uD7Kg4uMcUVdY+EKESjm2SwK0FrSBJRvQ/mg
|
||||
JKC7rf2tx7XB3tiKPrmygtLzwyU18+drCMI7fpcrf7wgwyuSdH3klkqnjF/xchQ9
|
||||
RkT3ZDR6mpxhv/ytoXrhAoIBAQDISEKgj/Z+2bNLhryvRfQACzvLHVp59oyI5Xa6
|
||||
MxLIVtozpq05wJxUgY4iPVXLx1Vm9/osHhXQtsFwMQTG2RI0tcz4N7YXrH4acmlm
|
||||
ErdoORtcRX15mEVAl7Mwac4LVyllOZ1D9woKboHDmlBO2L8FUXy8RiLdUT0jgK7k
|
||||
ShWb35twbqwQDLezLEiMnxKFCarVFVBTxVn2bhRA5jcPU+9S2oK9Qx/Mei7QlKKE
|
||||
uTXLOTtNGSvY/7h0dExzS8nXwRDvsVCrWCT5pca1KfmR/JPOPbM6I/vwziSIqMNk
|
||||
GfZWe5IlsQRtyZ49DpA+eDQxzMhxjZhWQ6JR5iQFWUCtXKIhAoIBAQC7P+pPEf9l
|
||||
KOhdPJu6p9NPQu6+hMTi5rTyCDsH5VggvoLKDZTJ1BSqWtW1K09UafRWP5vOxm7u
|
||||
fBYcnqu0W5RSUuoQTZiu03ZhYLBV5vbR+Icx0Hc2BDl8eEIyevyqsmm8+w5i28Ar
|
||||
knep4sP7/n+q2EAK1B2ZlNXXz6f47CMQMkVvZp3FR0F7R6yJoS32tTL5wDOxuOFG
|
||||
LYQOG/yI5JWXwBXov83zrpc+C7kl4gV3xAOk7fZ0exoRdmuvdLS96Ans85L7J8RW
|
||||
ELSfhmGahM+SQ1oJMcV/wYqF2qeLL2F8DZbjR5izLZgkNz4a/VMl/A6YHtuTBXAY
|
||||
+5PXXUOX+9Y9AoIBACI+II4dLxLPG9WM6tO4zRf407dNhHuXyL1bJip9svdnyhTM
|
||||
qY9XPCNCp095VyLpKNPbD/3dAvPVW0tYRi3NTUyPzMSfmdWAW2sgJp8aEhuSr/fd
|
||||
ta9Fdomtpihf3qeXtm8lI5tMMH5KGIud5Z8ldbtuDDqQb0ORsTdRuBU2CW3GFGhr
|
||||
s6Vm1z2eE6VfSSZP2dJmu34nHtOATJwwADfxrNhonbPINzaZqUlmMEcq92SQm2/6
|
||||
HsISLrJSdAO+cHsf+kpQ8a7p+iBo1ImC7LWmDotTh0IohtnMFPj8ibOisLhmlj01
|
||||
f8FZmGFuDQFxQdNF5PttLx+InscL5xq3ANTjIqECggEADpdtd9nsMALfEJzveb0o
|
||||
P0308s2/1fqqcQ3pI7Vgh7Sw1nP2ez/WmGvZqXOFjAtxqeLtDlDyRg1PX82Rjc1x
|
||||
InUpnjmdw0nhOLdjJl6IL1aRmnUnRQNRQ3zPk8V3uQmMKdjahyOetwaD4q40HYf4
|
||||
hOSzIOTkpZoui9G3wjMMjG+Ob57sfnoOBUBRlqwDu+zk2wd6P8grbd+QIdVWeYhu
|
||||
i9PBIVEJCIs7Z+9b7zLMwEd7DTgp82vAXUoAHD0Y9I+HbnqQope3ugk1OhUrt/HP
|
||||
hxNOidbiEBGR7NpcIgGAND2O24kxwgy0hWX0pf/FofkhXgNRkwRidt/r5mVzJf3O
|
||||
9QKCAQAcPXczJY1gynUA8uD/1ODmjpDjWAk0EKBEWY5X2oULv2+xGMNTbT8pwE3f
|
||||
1rszdtF3ckDPoBn7XS9OJwHnVHfXZNJHBtq9utLu0ccE+29HRG0pLCzATsvtoBWi
|
||||
MEwZ2mPqhVpktfqEnL27l/QHkP7dNOyh+halVCHMfy1aNMY6hsKrOcmVmYHVARX0
|
||||
Np2sG9zQszE0+t2mf8Pfd7cEvVuSTIfYZnW+77+PaVkICXXX0rrvwXVh/DVXwmWH
|
||||
kYbDIdiNs9NEFwCmIvzLVsCp0qGUuq9txYo/ML5PMzJhN6X3U+rV42GkkT7KxwH2
|
||||
Izss0+mp4ijKEFQuCGCkxjFmxUEq
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
@@ -35,6 +35,15 @@ services:
|
||||
timeout: 3s
|
||||
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:
|
||||
image: apache/kafka:4.0.1
|
||||
container_name: juwan-kafka
|
||||
@@ -77,40 +86,42 @@ services:
|
||||
condition: service_started
|
||||
|
||||
envoy-gateway:
|
||||
image: envoyproxy/envoy:v1.31-latest
|
||||
container_name: juwan-envoy-gateway
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- /usr/local/bin/envoy
|
||||
- -c
|
||||
- /etc/envoy/envoy.yaml
|
||||
- --log-level
|
||||
- info
|
||||
build:
|
||||
context: ../deploy/dev/envoy
|
||||
image: envoy-gateway:latest
|
||||
container_name: ${ENVOY_GATEWAY_CONTAINER_NAME:-envoy-gateway-dev-server}
|
||||
ports:
|
||||
- "18080:8080"
|
||||
volumes:
|
||||
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
|
||||
- "8080:8080"
|
||||
- "9901:9901"
|
||||
depends_on:
|
||||
authz-adapter:
|
||||
condition: service_started
|
||||
users-api:
|
||||
condition: service_started
|
||||
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:
|
||||
required: false
|
||||
user-api:
|
||||
condition: service_started
|
||||
required: false
|
||||
email-api:
|
||||
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 层 ====================
|
||||
user-rpc:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: grafana-admin
|
||||
namespace: monitoring
|
||||
type: Opaque
|
||||
data:
|
||||
admin-user: YWRtaW4=
|
||||
admin-password: Y2hhbmdlLW1l
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-datasources
|
||||
namespace: monitoring
|
||||
data:
|
||||
datasources.yaml: |
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
- name: Loki
|
||||
type: loki
|
||||
access: proxy
|
||||
url: http://loki:3100
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: grafana
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: grafana
|
||||
spec:
|
||||
containers:
|
||||
- name: grafana
|
||||
image: grafana/grafana:10.4.6
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
env:
|
||||
- name: GF_SECURITY_ADMIN_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin
|
||||
key: admin-user
|
||||
- name: GF_SECURITY_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin
|
||||
key: admin-password
|
||||
volumeMounts:
|
||||
- name: datasources
|
||||
mountPath: /etc/grafana/provisioning/datasources
|
||||
volumes:
|
||||
- name: datasources
|
||||
configMap:
|
||||
name: grafana-datasources
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: http
|
||||
selector:
|
||||
app: grafana
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: grafana-admin
|
||||
namespace: monitoring
|
||||
type: Opaque
|
||||
data:
|
||||
admin-user: YWRtaW4=
|
||||
admin-password: Y2hhbmdlLW1l
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-datasources
|
||||
namespace: monitoring
|
||||
data:
|
||||
datasources.yaml: |
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
- name: Loki
|
||||
type: loki
|
||||
access: proxy
|
||||
url: http://loki:3100
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: grafana
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: grafana
|
||||
spec:
|
||||
containers:
|
||||
- name: grafana
|
||||
image: grafana/grafana:10.4.6
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
env:
|
||||
- name: GF_SECURITY_ADMIN_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin
|
||||
key: admin-user
|
||||
- name: GF_SECURITY_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin
|
||||
key: admin-password
|
||||
volumeMounts:
|
||||
- name: datasources
|
||||
mountPath: /etc/grafana/provisioning/datasources
|
||||
volumes:
|
||||
- name: datasources
|
||||
configMap:
|
||||
name: grafana-datasources
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: http
|
||||
selector:
|
||||
app: grafana
|
||||
|
||||
@@ -1,90 +1,90 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: loki-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
loki.yaml: |
|
||||
auth_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 3100
|
||||
|
||||
common:
|
||||
path_prefix: /loki
|
||||
storage:
|
||||
filesystem:
|
||||
chunks_directory: /loki/chunks
|
||||
rules_directory: /loki/rules
|
||||
replication_factor: 1
|
||||
ring:
|
||||
kvstore:
|
||||
store: inmemory
|
||||
|
||||
schema_config:
|
||||
configs:
|
||||
- from: 2024-01-01
|
||||
store: boltdb-shipper
|
||||
object_store: filesystem
|
||||
schema: v12
|
||||
index:
|
||||
prefix: index_
|
||||
period: 24h
|
||||
|
||||
storage_config:
|
||||
boltdb_shipper:
|
||||
active_index_directory: /loki/index
|
||||
cache_location: /loki/cache
|
||||
shared_store: filesystem
|
||||
|
||||
ruler:
|
||||
alertmanager_url: http://localhost:9093
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: loki
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: loki
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: loki
|
||||
spec:
|
||||
containers:
|
||||
- name: loki
|
||||
image: grafana/loki:2.9.6
|
||||
args:
|
||||
- "-config.file=/etc/loki/loki.yaml"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3100
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/loki
|
||||
- name: data
|
||||
mountPath: /loki
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: loki-config
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: loki
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 3100
|
||||
targetPort: http
|
||||
selector:
|
||||
app: loki
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: loki-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
loki.yaml: |
|
||||
auth_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 3100
|
||||
|
||||
common:
|
||||
path_prefix: /loki
|
||||
storage:
|
||||
filesystem:
|
||||
chunks_directory: /loki/chunks
|
||||
rules_directory: /loki/rules
|
||||
replication_factor: 1
|
||||
ring:
|
||||
kvstore:
|
||||
store: inmemory
|
||||
|
||||
schema_config:
|
||||
configs:
|
||||
- from: 2024-01-01
|
||||
store: boltdb-shipper
|
||||
object_store: filesystem
|
||||
schema: v12
|
||||
index:
|
||||
prefix: index_
|
||||
period: 24h
|
||||
|
||||
storage_config:
|
||||
boltdb_shipper:
|
||||
active_index_directory: /loki/index
|
||||
cache_location: /loki/cache
|
||||
shared_store: filesystem
|
||||
|
||||
ruler:
|
||||
alertmanager_url: http://localhost:9093
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: loki
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: loki
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: loki
|
||||
spec:
|
||||
containers:
|
||||
- name: loki
|
||||
image: grafana/loki:2.9.6
|
||||
args:
|
||||
- "-config.file=/etc/loki/loki.yaml"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3100
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/loki
|
||||
- name: data
|
||||
mountPath: /loki
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: loki-config
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: loki
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 3100
|
||||
targetPort: http
|
||||
selector:
|
||||
app: loki
|
||||
|
||||
@@ -1,138 +1,138 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: prometheus
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- nodes
|
||||
- nodes/metrics
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
- namespaces
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["extensions", "apps"]
|
||||
resources:
|
||||
- deployments
|
||||
verbs: ["get", "list", "watch"]
|
||||
- nonResourceURLs: ["/metrics"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: prometheus
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: prometheus
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: prometheus-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
prometheus.yml: |
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: "prometheus"
|
||||
static_configs:
|
||||
- targets: ["localhost:9090"]
|
||||
|
||||
- job_name: "kubernetes-annotated-endpoints"
|
||||
kubernetes_sd_configs:
|
||||
- role: endpoints
|
||||
relabel_configs:
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
|
||||
action: keep
|
||||
regex: "true"
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
|
||||
action: replace
|
||||
target_label: __scheme__
|
||||
regex: (https?)
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
|
||||
action: replace
|
||||
target_label: __metrics_path__
|
||||
regex: (.+)
|
||||
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
|
||||
action: replace
|
||||
target_label: __address__
|
||||
regex: (.+):(?:\d+);(\d+)
|
||||
replacement: $1:$2
|
||||
- source_labels: [__meta_kubernetes_namespace]
|
||||
action: replace
|
||||
target_label: namespace
|
||||
- source_labels: [__meta_kubernetes_service_name]
|
||||
action: replace
|
||||
target_label: service
|
||||
- source_labels: [__meta_kubernetes_endpoint_port_name]
|
||||
action: replace
|
||||
target_label: port
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: prometheus
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: prometheus
|
||||
spec:
|
||||
serviceAccountName: prometheus
|
||||
containers:
|
||||
- name: prometheus
|
||||
image: prom/prometheus:v2.53.0
|
||||
args:
|
||||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||
- "--storage.tsdb.path=/prometheus"
|
||||
- "--storage.tsdb.retention.time=15d"
|
||||
- "--web.enable-lifecycle"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 9090
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/prometheus
|
||||
- name: data
|
||||
mountPath: /prometheus
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: prometheus-config
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 9090
|
||||
targetPort: http
|
||||
selector:
|
||||
app: prometheus
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: prometheus
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- nodes
|
||||
- nodes/metrics
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
- namespaces
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["extensions", "apps"]
|
||||
resources:
|
||||
- deployments
|
||||
verbs: ["get", "list", "watch"]
|
||||
- nonResourceURLs: ["/metrics"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: prometheus
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: prometheus
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: prometheus-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
prometheus.yml: |
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: "prometheus"
|
||||
static_configs:
|
||||
- targets: ["localhost:9090"]
|
||||
|
||||
- job_name: "kubernetes-annotated-endpoints"
|
||||
kubernetes_sd_configs:
|
||||
- role: endpoints
|
||||
relabel_configs:
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
|
||||
action: keep
|
||||
regex: "true"
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
|
||||
action: replace
|
||||
target_label: __scheme__
|
||||
regex: (https?)
|
||||
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
|
||||
action: replace
|
||||
target_label: __metrics_path__
|
||||
regex: (.+)
|
||||
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
|
||||
action: replace
|
||||
target_label: __address__
|
||||
regex: (.+):(?:\d+);(\d+)
|
||||
replacement: $1:$2
|
||||
- source_labels: [__meta_kubernetes_namespace]
|
||||
action: replace
|
||||
target_label: namespace
|
||||
- source_labels: [__meta_kubernetes_service_name]
|
||||
action: replace
|
||||
target_label: service
|
||||
- source_labels: [__meta_kubernetes_endpoint_port_name]
|
||||
action: replace
|
||||
target_label: port
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: prometheus
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: prometheus
|
||||
spec:
|
||||
serviceAccountName: prometheus
|
||||
containers:
|
||||
- name: prometheus
|
||||
image: prom/prometheus:v2.53.0
|
||||
args:
|
||||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||
- "--storage.tsdb.path=/prometheus"
|
||||
- "--storage.tsdb.retention.time=15d"
|
||||
- "--web.enable-lifecycle"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 9090
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/prometheus
|
||||
- name: data
|
||||
mountPath: /prometheus
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: prometheus-config
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: monitoring
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 9090
|
||||
targetPort: http
|
||||
selector:
|
||||
app: prometheus
|
||||
|
||||
+149
-149
@@ -1,149 +1,149 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: promtail
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- nodes
|
||||
- pods
|
||||
- pods/log
|
||||
- services
|
||||
- endpoints
|
||||
- namespaces
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: promtail
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: promtail
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: promtail-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
promtail.yaml: |
|
||||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /run/promtail/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://loki:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
- job_name: kubernetes-pods
|
||||
kubernetes_sd_configs:
|
||||
- role: pod
|
||||
relabel_configs:
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
|
||||
target_label: app
|
||||
regex: (.+)
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_label_app]
|
||||
target_label: app
|
||||
regex: (.+)
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_node_name]
|
||||
target_label: node
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_namespace]
|
||||
target_label: namespace
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_name]
|
||||
target_label: pod
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_container_name]
|
||||
target_label: container
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
|
||||
separator: /
|
||||
target_label: __path__
|
||||
replacement: /var/log/pods/*$1/*.log
|
||||
- job_name: kubernetes-pods-static
|
||||
pipeline_stages:
|
||||
- regex:
|
||||
source: filename
|
||||
expression: /var/log/pods/(?P<namespace>[^_]+)_(?P<pod>[^_]+)_[^/]+/(?P<container>[^/]+)/[0-9]+\.log
|
||||
- regex:
|
||||
source: pod
|
||||
expression: ^(?P<app>.+?)(?:-[a-f0-9]{8,10}-[a-z0-9]{5}|-[0-9]+)?$
|
||||
- labels:
|
||||
namespace:
|
||||
pod:
|
||||
container:
|
||||
app:
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: kubernetes-pods
|
||||
__path__: /var/log/pods/*/*/*.log
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: promtail
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: promtail
|
||||
spec:
|
||||
serviceAccountName: promtail
|
||||
tolerations:
|
||||
- operator: "Exists"
|
||||
containers:
|
||||
- name: promtail
|
||||
image: grafana/promtail:2.9.6
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
args:
|
||||
- "-config.file=/etc/promtail/promtail.yaml"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/promtail
|
||||
- name: positions
|
||||
mountPath: /run/promtail
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
readOnly: true
|
||||
- name: dockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: promtail-config
|
||||
- name: positions
|
||||
emptyDir: {}
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: dockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: promtail
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- nodes
|
||||
- pods
|
||||
- pods/log
|
||||
- services
|
||||
- endpoints
|
||||
- namespaces
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: promtail
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: promtail
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: promtail-config
|
||||
namespace: monitoring
|
||||
data:
|
||||
promtail.yaml: |
|
||||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /run/promtail/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://loki:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
- job_name: kubernetes-pods
|
||||
kubernetes_sd_configs:
|
||||
- role: pod
|
||||
relabel_configs:
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
|
||||
target_label: app
|
||||
regex: (.+)
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_label_app]
|
||||
target_label: app
|
||||
regex: (.+)
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_node_name]
|
||||
target_label: node
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_namespace]
|
||||
target_label: namespace
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_name]
|
||||
target_label: pod
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_container_name]
|
||||
target_label: container
|
||||
- action: replace
|
||||
source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
|
||||
separator: /
|
||||
target_label: __path__
|
||||
replacement: /var/log/pods/*$1/*.log
|
||||
- job_name: kubernetes-pods-static
|
||||
pipeline_stages:
|
||||
- regex:
|
||||
source: filename
|
||||
expression: /var/log/pods/(?P<namespace>[^_]+)_(?P<pod>[^_]+)_[^/]+/(?P<container>[^/]+)/[0-9]+\.log
|
||||
- regex:
|
||||
source: pod
|
||||
expression: ^(?P<app>.+?)(?:-[a-f0-9]{8,10}-[a-z0-9]{5}|-[0-9]+)?$
|
||||
- labels:
|
||||
namespace:
|
||||
pod:
|
||||
container:
|
||||
app:
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: kubernetes-pods
|
||||
__path__: /var/log/pods/*/*/*.log
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: promtail
|
||||
namespace: monitoring
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: promtail
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: promtail
|
||||
spec:
|
||||
serviceAccountName: promtail
|
||||
tolerations:
|
||||
- operator: "Exists"
|
||||
containers:
|
||||
- name: promtail
|
||||
image: grafana/promtail:2.9.6
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
args:
|
||||
- "-config.file=/etc/promtail/promtail.yaml"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/promtail
|
||||
- name: positions
|
||||
mountPath: /run/promtail
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
readOnly: true
|
||||
- name: dockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: promtail-config
|
||||
- name: positions
|
||||
emptyDir: {}
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: dockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: jwt-secret
|
||||
namespace: juwan
|
||||
type: Opaque
|
||||
data:
|
||||
secret-key: MGUyMWE3ZDhjMTQ5ZDg1MWViOWU0MGM3OTE2NWVkYTBlOTE5ZWRkZDU1YjYzOGJjOWRiNzM0NTc4NDIyMjlkZQ==
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: jwt-secret-reader
|
||||
namespace: juwan
|
||||
rules:
|
||||
# JWT Secret 读取权限
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["jwt-secret"]
|
||||
verbs: ["get"]
|
||||
# 服务发现权限
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["discovery.k8s.io"]
|
||||
resources: ["endpointslices"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: user-rpc-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: envoy-gateway-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: jwt-secret
|
||||
namespace: juwan
|
||||
type: Opaque
|
||||
data:
|
||||
secret-key: MGUyMWE3ZDhjMTQ5ZDg1MWViOWU0MGM3OTE2NWVkYTBlOTE5ZWRkZDU1YjYzOGJjOWRiNzM0NTc4NDIyMjlkZQ==
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: jwt-secret-reader
|
||||
namespace: juwan
|
||||
rules:
|
||||
# JWT Secret 读取权限
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["jwt-secret"]
|
||||
verbs: ["get"]
|
||||
# 服务发现权限
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["discovery.k8s.io"]
|
||||
resources: ["endpointslices"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: user-rpc-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: envoy-gateway-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
|
||||
+1032
-1032
File diff suppressed because it is too large
Load Diff
+108
-108
@@ -1,108 +1,108 @@
|
||||
# Envoy Gateway Configuration
|
||||
|
||||
This document explains how the Envoy unified ingress gateway is configured and how to modify it.
|
||||
|
||||
## Files
|
||||
|
||||
- deploy/k8s/envoy/envoy.yaml: ConfigMap + Deployment + Service for Envoy
|
||||
|
||||
## Current Behavior
|
||||
|
||||
- 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/email` to `email-api-svc:8888`.
|
||||
- Route `/healthz` returns `200 ok` directly from gateway.
|
||||
- Unknown routes return `404` from gateway.
|
||||
|
||||
## Routing
|
||||
|
||||
In envoy.yaml, routes are defined under:
|
||||
|
||||
static_resources -> listeners -> http_connection_manager -> route_config -> virtual_hosts
|
||||
|
||||
The current routing rules are:
|
||||
|
||||
- `prefix: /api/users` -> `cluster: user_api_cluster`
|
||||
- `prefix: /api/email` -> `cluster: email_api_cluster`
|
||||
- `path: /healthz` -> direct response `200`
|
||||
- `prefix: /` -> direct response `404`
|
||||
|
||||
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`
|
||||
|
||||
1) Add a route match:
|
||||
|
||||
- match:
|
||||
prefix: "/api/order"
|
||||
route:
|
||||
cluster: order_api_cluster
|
||||
|
||||
1) Add a cluster:
|
||||
|
||||
- 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-svc.juwan.svc.cluster.local
|
||||
port_value: 8899
|
||||
|
||||
## CSRF Protection (Double Cookie)
|
||||
|
||||
Envoy uses a Lua filter for double-cookie CSRF validation:
|
||||
|
||||
- Safe methods (GET/HEAD/OPTIONS):
|
||||
- If missing, Envoy auto-issues two cookies:
|
||||
- `csrf_token`
|
||||
- `csrf_guard`
|
||||
- Unsafe methods (POST/PUT/PATCH/DELETE, etc):
|
||||
- Requires BOTH headers:
|
||||
- `X-CSRF-Token`
|
||||
- `X-CSRF-Guard`
|
||||
- Requires BOTH cookies:
|
||||
- `csrf_token`
|
||||
- `csrf_guard`
|
||||
- Header values must exactly match cookie values, otherwise Envoy returns `403`.
|
||||
|
||||
If you want different cookie or header names, update these constants in Lua:
|
||||
|
||||
- `TOKEN_COOKIE`
|
||||
- `GUARD_COOKIE`
|
||||
- `TOKEN_HEADER`
|
||||
- `GUARD_HEADER`
|
||||
|
||||
To relax or tighten rules, edit the functions:
|
||||
|
||||
- is_safe(method)
|
||||
- envoy_on_request(request_handle)
|
||||
|
||||
## Cookie Attributes
|
||||
|
||||
Current Set-Cookie:
|
||||
|
||||
- `csrf_token=<value>; Path=/; SameSite=Strict`
|
||||
- `csrf_guard=<value>; Path=/; SameSite=Strict`
|
||||
|
||||
## Deployment
|
||||
|
||||
Apply or update:
|
||||
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
## Common Changes
|
||||
|
||||
- Change listening port:
|
||||
- Update listener port_value and Service targetPort/port.
|
||||
- Change service namespace:
|
||||
- Update cluster DNS addresses (e.g. `service.ns.svc.cluster.local`).
|
||||
- Add more services:
|
||||
- Add route + add cluster, as shown above.
|
||||
- Update CSRF policy:
|
||||
- Edit Lua validation logic in `envoy.filters.http.lua`.
|
||||
# Envoy Gateway Configuration
|
||||
|
||||
This document explains how the Envoy unified ingress gateway is configured and how to modify it.
|
||||
|
||||
## Files
|
||||
|
||||
- deploy/k8s/envoy/envoy.yaml: ConfigMap + Deployment + Service for Envoy
|
||||
|
||||
## Current Behavior
|
||||
|
||||
- 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/email` to `email-api-svc:8888`.
|
||||
- Route `/healthz` returns `200 ok` directly from gateway.
|
||||
- Unknown routes return `404` from gateway.
|
||||
|
||||
## Routing
|
||||
|
||||
In envoy.yaml, routes are defined under:
|
||||
|
||||
static_resources -> listeners -> http_connection_manager -> route_config -> virtual_hosts
|
||||
|
||||
The current routing rules are:
|
||||
|
||||
- `prefix: /api/users` -> `cluster: user_api_cluster`
|
||||
- `prefix: /api/email` -> `cluster: email_api_cluster`
|
||||
- `path: /healthz` -> direct response `200`
|
||||
- `prefix: /` -> direct response `404`
|
||||
|
||||
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`
|
||||
|
||||
1) Add a route match:
|
||||
|
||||
- match:
|
||||
prefix: "/api/order"
|
||||
route:
|
||||
cluster: order_api_cluster
|
||||
|
||||
1) Add a cluster:
|
||||
|
||||
- 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-svc.juwan.svc.cluster.local
|
||||
port_value: 8899
|
||||
|
||||
## CSRF Protection (Double Cookie)
|
||||
|
||||
Envoy uses a Lua filter for double-cookie CSRF validation:
|
||||
|
||||
- Safe methods (GET/HEAD/OPTIONS):
|
||||
- If missing, Envoy auto-issues two cookies:
|
||||
- `csrf_token`
|
||||
- `csrf_guard`
|
||||
- Unsafe methods (POST/PUT/PATCH/DELETE, etc):
|
||||
- Requires BOTH headers:
|
||||
- `X-CSRF-Token`
|
||||
- `X-CSRF-Guard`
|
||||
- Requires BOTH cookies:
|
||||
- `csrf_token`
|
||||
- `csrf_guard`
|
||||
- Header values must exactly match cookie values, otherwise Envoy returns `403`.
|
||||
|
||||
If you want different cookie or header names, update these constants in Lua:
|
||||
|
||||
- `TOKEN_COOKIE`
|
||||
- `GUARD_COOKIE`
|
||||
- `TOKEN_HEADER`
|
||||
- `GUARD_HEADER`
|
||||
|
||||
To relax or tighten rules, edit the functions:
|
||||
|
||||
- is_safe(method)
|
||||
- envoy_on_request(request_handle)
|
||||
|
||||
## Cookie Attributes
|
||||
|
||||
Current Set-Cookie:
|
||||
|
||||
- `csrf_token=<value>; Path=/; SameSite=Strict`
|
||||
- `csrf_guard=<value>; Path=/; SameSite=Strict`
|
||||
|
||||
## Deployment
|
||||
|
||||
Apply or update:
|
||||
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
## Common Changes
|
||||
|
||||
- Change listening port:
|
||||
- Update listener port_value and Service targetPort/port.
|
||||
- Change service namespace:
|
||||
- Update cluster DNS addresses (e.g. `service.ns.svc.cluster.local`).
|
||||
- Add more services:
|
||||
- Add route + add cluster, as shown above.
|
||||
- Update CSRF policy:
|
||||
- Edit Lua validation logic in `envoy.filters.http.lua`.
|
||||
|
||||
+385
-385
@@ -1,385 +1,385 @@
|
||||
# Kubernetes 部署问题排查与解决记录
|
||||
|
||||
**日期**: 2026年2月23日
|
||||
**问题**: user-rpc 和 Redis 部署失败
|
||||
**状态**: 已诊断,解决中
|
||||
|
||||
---
|
||||
|
||||
## 📋 问题描述
|
||||
|
||||
执行 `kubectl apply -f test.yaml` 后,资源虽然创建成功,但实际的应用 pods 并未正常运行:
|
||||
|
||||
```
|
||||
kubectl apply -f ..\test.yaml
|
||||
✓ deployment.apps/user-rpc created
|
||||
✓ service/user-rpc-svc created
|
||||
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-c created
|
||||
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-m created
|
||||
✓ redisreplication.redis.redis.opstreelabs.in/user-redis created
|
||||
✓ redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created
|
||||
✓ cluster.postgresql.cnpg.io/user-db created
|
||||
```
|
||||
|
||||
但执行 `kubectl get all` 后,发现:
|
||||
- ❌ **user-rpc pods 未创建**(Deployment 0/3 replicas ready)
|
||||
- ❌ **Redis pods 未创建**(RedisReplication 资源存在但无 pods)
|
||||
- ✅ user-db pods 正常运行(3/3)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 排查过程
|
||||
|
||||
### 第一步:检查 Deployment 状态
|
||||
|
||||
```bash
|
||||
kubectl describe deployment user-rpc
|
||||
```
|
||||
|
||||
**发现**:
|
||||
```
|
||||
Conditions:
|
||||
Type Status Reason
|
||||
---- ------ ------
|
||||
Progressing True NewReplicaSetCreated
|
||||
Available False MinimumReplicasUnavailable
|
||||
ReplicaFailure True FailedCreate
|
||||
```
|
||||
|
||||
### 第二步:检查 ReplicaSet 详情
|
||||
|
||||
```bash
|
||||
kubectl describe replicaset user-rpc-6bf77fbcd9
|
||||
```
|
||||
|
||||
**发现关键错误**:
|
||||
```
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Warning FailedCreate 3m53s replicaset-controller Error creating:
|
||||
pods "user-rpc-6bf77fbcd9-" is forbidden: error looking up service
|
||||
account default/find-endpoints: serviceaccount "find-endpoints" not found
|
||||
```
|
||||
|
||||
**问题 #1 诊断完成**:❌ **缺失 ServiceAccount "find-endpoints"**
|
||||
|
||||
### 第三步:检查现有 ServiceAccounts
|
||||
|
||||
```bash
|
||||
kubectl get serviceaccount
|
||||
```
|
||||
|
||||
**结果**:
|
||||
```
|
||||
NAME AGE
|
||||
cluster-example 4d10h
|
||||
default 13d
|
||||
redis-operator 9h
|
||||
user-db 4m9s
|
||||
```
|
||||
|
||||
确认 `find-endpoints` 不存在。
|
||||
|
||||
### 第四步:检查 Secrets
|
||||
|
||||
```bash
|
||||
kubectl get secrets
|
||||
```
|
||||
|
||||
**结果**:默认 secrets 都存在,包括:
|
||||
- ✅ user-db-app
|
||||
- ✅ user-redis
|
||||
- ✅ user-db-ca, user-db-replication, user-db-server
|
||||
|
||||
### 第五步:检查 Redis 部署
|
||||
|
||||
```bash
|
||||
kubectl get redisreplication
|
||||
kubectl get pods | grep redis
|
||||
```
|
||||
|
||||
**发现**:
|
||||
- ✅ RedisReplication 资源存在
|
||||
- ❌ Redis pods **完全没有被创建**
|
||||
|
||||
**问题 #2 诊断**:❌ **Redis Operator 未响应 RedisReplication 资源**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 第一次修复尝试
|
||||
|
||||
### 创建缺失的 ServiceAccount
|
||||
|
||||
```bash
|
||||
kubectl create serviceaccount find-endpoints
|
||||
```
|
||||
|
||||
**结果**:✅ ServiceAccount 创建成功
|
||||
|
||||
### 重启 Deployment
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment user-rpc
|
||||
```
|
||||
|
||||
**等待 5-10 秒后重新检查**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
**新的发现**:
|
||||
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
user-rpc-66f97fbdcc-ws7rc 0/1 ErrImagePull 0 26s
|
||||
user-rpc-6bf77fbcd9-njm2z 0/1 ErrImagePull 0 29s
|
||||
user-rpc-6bf77fbcd9-nwjtw 0/1 ImagePullBackOff 0 29s
|
||||
user-rpc-6bf77fbcd9-wjrf8 0/1 ErrImagePull 0 29s
|
||||
```
|
||||
|
||||
✅ **好消息**:Pods 现在被创建了!(说明 ServiceAccount 问题已解决)
|
||||
❌ **新问题**:镜像拉取失败
|
||||
|
||||
---
|
||||
|
||||
## 🐛 根因分析
|
||||
|
||||
### 问题 #1:缺失 ServiceAccount ✅ 已解决
|
||||
|
||||
**根本原因**:test.yaml 的 Deployment manifest 指定了:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: find-endpoints # 这个 ServiceAccount 不存在
|
||||
```
|
||||
|
||||
但没有在 test.yaml 中创建 ServiceAccount 资源。
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
kubectl create serviceaccount find-endpoints
|
||||
```
|
||||
|
||||
或在 test.yaml 中添加:
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: find-endpoints
|
||||
namespace: default
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 问题 #2:镜像拉取失败 ❌ 需要修复
|
||||
|
||||
```bash
|
||||
kubectl describe pod user-rpc-6bf77fbcd9-njm2z
|
||||
```
|
||||
|
||||
**详细错误日志**:
|
||||
|
||||
```
|
||||
Events:
|
||||
Warning Failed 38s kubelet Failed to pull image
|
||||
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
|
||||
Error response from daemon: Get "https://103.236.53.208:4418/v2/":
|
||||
context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
|
||||
Warning Failed 23s kubelet Failed to pull image
|
||||
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
|
||||
http: server gave HTTP response to HTTPS client
|
||||
```
|
||||
|
||||
**根本原因分析**:
|
||||
|
||||
1. **网络连接失败**:`context deadline exceeded` - 无法连接到镜像仓库
|
||||
2. **协议不匹配**:`http: server gave HTTP response to HTTPS client` -
|
||||
- 地址 `103.236.53.208:4418` 应该是 HTTP 而不是 HTTPS
|
||||
- Docker daemon 尝试用 HTTPS 连接,但服务器使用 HTTP
|
||||
|
||||
**可能原因**:
|
||||
- 镜像仓库地址错误或不可访问
|
||||
- 镜像仓库需要特定的网络配置
|
||||
- 仓库服务器离线或配置不当
|
||||
|
||||
---
|
||||
|
||||
### 问题 #3:Redis 部署失败 ❌ 需要诊断
|
||||
|
||||
**现象**:
|
||||
- RedisReplication 和 RedisSentinel CRD 资源创建成功
|
||||
- 但没有对应的 Redis pods 被创建
|
||||
- `kubectl get pods | grep redis` 返回空
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. **Redis Operator 未正常工作**
|
||||
- Operator pod 可能存在问题
|
||||
- Operator 未能监听到新的 RedisReplication 资源
|
||||
|
||||
2. **CRD 或 API 版本问题**
|
||||
- manifest 中使用的 API 版本 `v1beta2` 可能不匹配 Operator 版本
|
||||
|
||||
3. **资源限制或权限问题**
|
||||
- Operator 无权限创建 pods
|
||||
- 集群资源限制阻止了 pod 创建
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已执行的修复
|
||||
|
||||
| # | 问题 | 修复方法 | 状态 |
|
||||
|---|------|---------|------|
|
||||
| 1 | 缺失 ServiceAccount | `kubectl create serviceaccount find-endpoints` | ✅ 完成 |
|
||||
| 2 | 镜像拉取失败 | 需要更新镜像地址或修复网络 | ⏳ 待处理 |
|
||||
| 3 | Redis pods 未创建 | 需要诊断 Operator 日志 | ⏳ 待诊断 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步解决方案
|
||||
|
||||
### 优先级 1:修复 user-rpc 镜像拉取
|
||||
|
||||
**选项 A:使用本地/内部镜像**
|
||||
```yaml
|
||||
# 修改 test.yaml 中的镜像地址
|
||||
image: localhost:5000/user-rpc:latest # 本地私有仓库
|
||||
# 或
|
||||
image: user-rpc:latest # 本地镜像(如果已通过 docker load 导入)
|
||||
```
|
||||
|
||||
**选项 B:修复仓库地址**
|
||||
```yaml
|
||||
# 如果 103.236.53.208:4418 确实是正确仓库
|
||||
image: http://103.236.53.208:4418/library/user-rpc:latest # 显式使用 HTTP
|
||||
```
|
||||
|
||||
**验证步骤**:
|
||||
```bash
|
||||
# 检查镜像仓库连接性
|
||||
curl -v http://103.236.53.208:4418/v2/
|
||||
```
|
||||
|
||||
### 优先级 2:诊断 Redis Operator
|
||||
|
||||
```bash
|
||||
# 查看 Operator 日志
|
||||
kubectl logs -l app.kubernetes.io/name=redis-operator -f
|
||||
|
||||
# 查看 Operator pod
|
||||
kubectl get pods -A | grep redis-operator
|
||||
|
||||
# 查看 RedisReplication 详细信息
|
||||
kubectl describe redisreplication user-redis
|
||||
|
||||
# 检查 Operator 权限(RBAC)
|
||||
kubectl get role,rolebinding,clusterrole,clusterrolebinding | grep redis
|
||||
```
|
||||
|
||||
### 优先级 3:增强 test.yaml
|
||||
|
||||
建议在 test.yaml 中添加缺失的资源定义:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: find-endpoints
|
||||
namespace: default
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: registry-credentials
|
||||
namespace: default
|
||||
type: kubernetes.io/dockercfg
|
||||
data:
|
||||
.dockercfg: <base64-encoded-credentials> # 如果需要私有仓库认证
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前集群状态
|
||||
|
||||
### Pods 状态总结
|
||||
|
||||
| 应用 | 期望副本 | 实际运行 | 状态 |
|
||||
|------|---------|---------|------|
|
||||
| user-db | 3 | 3 | ✅ 正常 |
|
||||
| user-rpc | 3 | 0 | ❌ 镜像拉取失败 |
|
||||
| Redis | 3 | 0 | ❌ Operator 未创建 |
|
||||
| Sentinel | 3 | 0 | ❌ Operator 未创建 |
|
||||
|
||||
### Services 状态
|
||||
|
||||
```
|
||||
✅ kubernetes (内置)
|
||||
✅ user-rpc-svc:9001
|
||||
✅ user-db-r:5432 (只读副本)
|
||||
✅ user-db-ro:5432 (只读副本)
|
||||
✅ user-db-rw:5432 (读写主副本)
|
||||
```
|
||||
|
||||
### HPA 配置
|
||||
|
||||
```
|
||||
✅ user-rpc-hpa-c (CPU 目标: 80%) - 无法工作(pods 未运行)
|
||||
✅ user-rpc-hpa-m (Memory 目标: 80%) - 无法工作(pods 未运行)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 关键命令速查表
|
||||
|
||||
```bash
|
||||
# 查看 Deployment 状态
|
||||
kubectl describe deployment user-rpc
|
||||
|
||||
# 查看 ReplicaSet 错误事件
|
||||
kubectl describe replicaset user-rpc-6bf77fbcd9
|
||||
|
||||
# 查看 Pod 详细错误
|
||||
kubectl describe pod user-rpc-6bf77fbcd9-njm2z
|
||||
|
||||
# 查看 Pod 日志
|
||||
kubectl logs user-rpc-6bf77fbcd9-njm2z
|
||||
|
||||
# 查看所有事件(按时间排序)
|
||||
kubectl get events --sort-by='.lastTimestamp'
|
||||
|
||||
# 查看特定命名空间的所有资源
|
||||
kubectl get all -n default
|
||||
|
||||
# 重新启动 deployment(强制重新创建 pods)
|
||||
kubectl rollout restart deployment user-rpc
|
||||
|
||||
# 查看 Operator 日志
|
||||
kubectl logs -l app.kubernetes.io/name=redis-operator
|
||||
|
||||
# 检查 CRD 注册状态
|
||||
kubectl api-resources | grep redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
| 问题 | 原因 | 解决状态 |
|
||||
|------|------|---------|
|
||||
| **ServiceAccount 缺失** | manifest 中声明但未创建 | ✅ **已解决** |
|
||||
| **镜像拉取失败** | 仓库地址不可达或协议不匹配 | ⏳ **待处理** |
|
||||
| **Redis 未部署** | Operator 未响应 CRD | ⏳ **待诊断** |
|
||||
|
||||
**建议行动**:
|
||||
1. 确认/修复 user-rpc 镜像地址
|
||||
2. 诊断 Redis Operator 状态
|
||||
3. 验证所有依赖的 ServiceAccounts 和 Secrets 是否存在
|
||||
4. 考虑在 test.yaml 中添加完整的资源定义,避免手工创建
|
||||
|
||||
# Kubernetes 部署问题排查与解决记录
|
||||
|
||||
**日期**: 2026年2月23日
|
||||
**问题**: user-rpc 和 Redis 部署失败
|
||||
**状态**: 已诊断,解决中
|
||||
|
||||
---
|
||||
|
||||
## 📋 问题描述
|
||||
|
||||
执行 `kubectl apply -f test.yaml` 后,资源虽然创建成功,但实际的应用 pods 并未正常运行:
|
||||
|
||||
```
|
||||
kubectl apply -f ..\test.yaml
|
||||
✓ deployment.apps/user-rpc created
|
||||
✓ service/user-rpc-svc created
|
||||
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-c created
|
||||
✓ horizontalpodautoscaler.autoscaling/user-rpc-hpa-m created
|
||||
✓ redisreplication.redis.redis.opstreelabs.in/user-redis created
|
||||
✓ redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created
|
||||
✓ cluster.postgresql.cnpg.io/user-db created
|
||||
```
|
||||
|
||||
但执行 `kubectl get all` 后,发现:
|
||||
- ❌ **user-rpc pods 未创建**(Deployment 0/3 replicas ready)
|
||||
- ❌ **Redis pods 未创建**(RedisReplication 资源存在但无 pods)
|
||||
- ✅ user-db pods 正常运行(3/3)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 排查过程
|
||||
|
||||
### 第一步:检查 Deployment 状态
|
||||
|
||||
```bash
|
||||
kubectl describe deployment user-rpc
|
||||
```
|
||||
|
||||
**发现**:
|
||||
```
|
||||
Conditions:
|
||||
Type Status Reason
|
||||
---- ------ ------
|
||||
Progressing True NewReplicaSetCreated
|
||||
Available False MinimumReplicasUnavailable
|
||||
ReplicaFailure True FailedCreate
|
||||
```
|
||||
|
||||
### 第二步:检查 ReplicaSet 详情
|
||||
|
||||
```bash
|
||||
kubectl describe replicaset user-rpc-6bf77fbcd9
|
||||
```
|
||||
|
||||
**发现关键错误**:
|
||||
```
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Warning FailedCreate 3m53s replicaset-controller Error creating:
|
||||
pods "user-rpc-6bf77fbcd9-" is forbidden: error looking up service
|
||||
account default/find-endpoints: serviceaccount "find-endpoints" not found
|
||||
```
|
||||
|
||||
**问题 #1 诊断完成**:❌ **缺失 ServiceAccount "find-endpoints"**
|
||||
|
||||
### 第三步:检查现有 ServiceAccounts
|
||||
|
||||
```bash
|
||||
kubectl get serviceaccount
|
||||
```
|
||||
|
||||
**结果**:
|
||||
```
|
||||
NAME AGE
|
||||
cluster-example 4d10h
|
||||
default 13d
|
||||
redis-operator 9h
|
||||
user-db 4m9s
|
||||
```
|
||||
|
||||
确认 `find-endpoints` 不存在。
|
||||
|
||||
### 第四步:检查 Secrets
|
||||
|
||||
```bash
|
||||
kubectl get secrets
|
||||
```
|
||||
|
||||
**结果**:默认 secrets 都存在,包括:
|
||||
- ✅ user-db-app
|
||||
- ✅ user-redis
|
||||
- ✅ user-db-ca, user-db-replication, user-db-server
|
||||
|
||||
### 第五步:检查 Redis 部署
|
||||
|
||||
```bash
|
||||
kubectl get redisreplication
|
||||
kubectl get pods | grep redis
|
||||
```
|
||||
|
||||
**发现**:
|
||||
- ✅ RedisReplication 资源存在
|
||||
- ❌ Redis pods **完全没有被创建**
|
||||
|
||||
**问题 #2 诊断**:❌ **Redis Operator 未响应 RedisReplication 资源**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 第一次修复尝试
|
||||
|
||||
### 创建缺失的 ServiceAccount
|
||||
|
||||
```bash
|
||||
kubectl create serviceaccount find-endpoints
|
||||
```
|
||||
|
||||
**结果**:✅ ServiceAccount 创建成功
|
||||
|
||||
### 重启 Deployment
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment user-rpc
|
||||
```
|
||||
|
||||
**等待 5-10 秒后重新检查**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
**新的发现**:
|
||||
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
user-rpc-66f97fbdcc-ws7rc 0/1 ErrImagePull 0 26s
|
||||
user-rpc-6bf77fbcd9-njm2z 0/1 ErrImagePull 0 29s
|
||||
user-rpc-6bf77fbcd9-nwjtw 0/1 ImagePullBackOff 0 29s
|
||||
user-rpc-6bf77fbcd9-wjrf8 0/1 ErrImagePull 0 29s
|
||||
```
|
||||
|
||||
✅ **好消息**:Pods 现在被创建了!(说明 ServiceAccount 问题已解决)
|
||||
❌ **新问题**:镜像拉取失败
|
||||
|
||||
---
|
||||
|
||||
## 🐛 根因分析
|
||||
|
||||
### 问题 #1:缺失 ServiceAccount ✅ 已解决
|
||||
|
||||
**根本原因**:test.yaml 的 Deployment manifest 指定了:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: find-endpoints # 这个 ServiceAccount 不存在
|
||||
```
|
||||
|
||||
但没有在 test.yaml 中创建 ServiceAccount 资源。
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
kubectl create serviceaccount find-endpoints
|
||||
```
|
||||
|
||||
或在 test.yaml 中添加:
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: find-endpoints
|
||||
namespace: default
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 问题 #2:镜像拉取失败 ❌ 需要修复
|
||||
|
||||
```bash
|
||||
kubectl describe pod user-rpc-6bf77fbcd9-njm2z
|
||||
```
|
||||
|
||||
**详细错误日志**:
|
||||
|
||||
```
|
||||
Events:
|
||||
Warning Failed 38s kubelet Failed to pull image
|
||||
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
|
||||
Error response from daemon: Get "https://103.236.53.208:4418/v2/":
|
||||
context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
|
||||
Warning Failed 23s kubelet Failed to pull image
|
||||
"103.236.53.208:4418/library/user-rpc@sha256:76b27d3eb4d5d44e...":
|
||||
http: server gave HTTP response to HTTPS client
|
||||
```
|
||||
|
||||
**根本原因分析**:
|
||||
|
||||
1. **网络连接失败**:`context deadline exceeded` - 无法连接到镜像仓库
|
||||
2. **协议不匹配**:`http: server gave HTTP response to HTTPS client` -
|
||||
- 地址 `103.236.53.208:4418` 应该是 HTTP 而不是 HTTPS
|
||||
- Docker daemon 尝试用 HTTPS 连接,但服务器使用 HTTP
|
||||
|
||||
**可能原因**:
|
||||
- 镜像仓库地址错误或不可访问
|
||||
- 镜像仓库需要特定的网络配置
|
||||
- 仓库服务器离线或配置不当
|
||||
|
||||
---
|
||||
|
||||
### 问题 #3:Redis 部署失败 ❌ 需要诊断
|
||||
|
||||
**现象**:
|
||||
- RedisReplication 和 RedisSentinel CRD 资源创建成功
|
||||
- 但没有对应的 Redis pods 被创建
|
||||
- `kubectl get pods | grep redis` 返回空
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. **Redis Operator 未正常工作**
|
||||
- Operator pod 可能存在问题
|
||||
- Operator 未能监听到新的 RedisReplication 资源
|
||||
|
||||
2. **CRD 或 API 版本问题**
|
||||
- manifest 中使用的 API 版本 `v1beta2` 可能不匹配 Operator 版本
|
||||
|
||||
3. **资源限制或权限问题**
|
||||
- Operator 无权限创建 pods
|
||||
- 集群资源限制阻止了 pod 创建
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已执行的修复
|
||||
|
||||
| # | 问题 | 修复方法 | 状态 |
|
||||
|---|------|---------|------|
|
||||
| 1 | 缺失 ServiceAccount | `kubectl create serviceaccount find-endpoints` | ✅ 完成 |
|
||||
| 2 | 镜像拉取失败 | 需要更新镜像地址或修复网络 | ⏳ 待处理 |
|
||||
| 3 | Redis pods 未创建 | 需要诊断 Operator 日志 | ⏳ 待诊断 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步解决方案
|
||||
|
||||
### 优先级 1:修复 user-rpc 镜像拉取
|
||||
|
||||
**选项 A:使用本地/内部镜像**
|
||||
```yaml
|
||||
# 修改 test.yaml 中的镜像地址
|
||||
image: localhost:5000/user-rpc:latest # 本地私有仓库
|
||||
# 或
|
||||
image: user-rpc:latest # 本地镜像(如果已通过 docker load 导入)
|
||||
```
|
||||
|
||||
**选项 B:修复仓库地址**
|
||||
```yaml
|
||||
# 如果 103.236.53.208:4418 确实是正确仓库
|
||||
image: http://103.236.53.208:4418/library/user-rpc:latest # 显式使用 HTTP
|
||||
```
|
||||
|
||||
**验证步骤**:
|
||||
```bash
|
||||
# 检查镜像仓库连接性
|
||||
curl -v http://103.236.53.208:4418/v2/
|
||||
```
|
||||
|
||||
### 优先级 2:诊断 Redis Operator
|
||||
|
||||
```bash
|
||||
# 查看 Operator 日志
|
||||
kubectl logs -l app.kubernetes.io/name=redis-operator -f
|
||||
|
||||
# 查看 Operator pod
|
||||
kubectl get pods -A | grep redis-operator
|
||||
|
||||
# 查看 RedisReplication 详细信息
|
||||
kubectl describe redisreplication user-redis
|
||||
|
||||
# 检查 Operator 权限(RBAC)
|
||||
kubectl get role,rolebinding,clusterrole,clusterrolebinding | grep redis
|
||||
```
|
||||
|
||||
### 优先级 3:增强 test.yaml
|
||||
|
||||
建议在 test.yaml 中添加缺失的资源定义:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: find-endpoints
|
||||
namespace: default
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: registry-credentials
|
||||
namespace: default
|
||||
type: kubernetes.io/dockercfg
|
||||
data:
|
||||
.dockercfg: <base64-encoded-credentials> # 如果需要私有仓库认证
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前集群状态
|
||||
|
||||
### Pods 状态总结
|
||||
|
||||
| 应用 | 期望副本 | 实际运行 | 状态 |
|
||||
|------|---------|---------|------|
|
||||
| user-db | 3 | 3 | ✅ 正常 |
|
||||
| user-rpc | 3 | 0 | ❌ 镜像拉取失败 |
|
||||
| Redis | 3 | 0 | ❌ Operator 未创建 |
|
||||
| Sentinel | 3 | 0 | ❌ Operator 未创建 |
|
||||
|
||||
### Services 状态
|
||||
|
||||
```
|
||||
✅ kubernetes (内置)
|
||||
✅ user-rpc-svc:9001
|
||||
✅ user-db-r:5432 (只读副本)
|
||||
✅ user-db-ro:5432 (只读副本)
|
||||
✅ user-db-rw:5432 (读写主副本)
|
||||
```
|
||||
|
||||
### HPA 配置
|
||||
|
||||
```
|
||||
✅ user-rpc-hpa-c (CPU 目标: 80%) - 无法工作(pods 未运行)
|
||||
✅ user-rpc-hpa-m (Memory 目标: 80%) - 无法工作(pods 未运行)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 关键命令速查表
|
||||
|
||||
```bash
|
||||
# 查看 Deployment 状态
|
||||
kubectl describe deployment user-rpc
|
||||
|
||||
# 查看 ReplicaSet 错误事件
|
||||
kubectl describe replicaset user-rpc-6bf77fbcd9
|
||||
|
||||
# 查看 Pod 详细错误
|
||||
kubectl describe pod user-rpc-6bf77fbcd9-njm2z
|
||||
|
||||
# 查看 Pod 日志
|
||||
kubectl logs user-rpc-6bf77fbcd9-njm2z
|
||||
|
||||
# 查看所有事件(按时间排序)
|
||||
kubectl get events --sort-by='.lastTimestamp'
|
||||
|
||||
# 查看特定命名空间的所有资源
|
||||
kubectl get all -n default
|
||||
|
||||
# 重新启动 deployment(强制重新创建 pods)
|
||||
kubectl rollout restart deployment user-rpc
|
||||
|
||||
# 查看 Operator 日志
|
||||
kubectl logs -l app.kubernetes.io/name=redis-operator
|
||||
|
||||
# 检查 CRD 注册状态
|
||||
kubectl api-resources | grep redis
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
| 问题 | 原因 | 解决状态 |
|
||||
|------|------|---------|
|
||||
| **ServiceAccount 缺失** | manifest 中声明但未创建 | ✅ **已解决** |
|
||||
| **镜像拉取失败** | 仓库地址不可达或协议不匹配 | ⏳ **待处理** |
|
||||
| **Redis 未部署** | Operator 未响应 CRD | ⏳ **待诊断** |
|
||||
|
||||
**建议行动**:
|
||||
1. 确认/修复 user-rpc 镜像地址
|
||||
2. 诊断 Redis Operator 状态
|
||||
3. 验证所有依赖的 ServiceAccounts 和 Secrets 是否存在
|
||||
4. 考虑在 test.yaml 中添加完整的资源定义,避免手工创建
|
||||
|
||||
|
||||
+1497
-1497
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
File diff suppressed because it is too large
Load Diff
+1068
-1068
File diff suppressed because it is too large
Load Diff
+424
-424
@@ -1,424 +1,424 @@
|
||||
# JWT Secret + ETCD Encryption Deployment Guide
|
||||
|
||||
完整的 JWT 认证系统部署指南,包括密钥管理、RBAC 权限控制和 ETCD 加密。
|
||||
|
||||
## 部署顺序
|
||||
|
||||
### 第1步:创建 Secret 和 RBAC(必需)
|
||||
|
||||
创建 JWT 秘钥和服务账户权限:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
验证创建成功:
|
||||
|
||||
```bash
|
||||
# 检查 Secret
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
kubectl get secret jwt-secret -n juwan -o yaml
|
||||
|
||||
# 检查 ServiceAccounts
|
||||
kubectl get sa user-rpc -n juwan
|
||||
kubectl get sa envoy-gateway -n juwan
|
||||
|
||||
# 检查 RBAC 权限
|
||||
kubectl get role jwt-secret-reader -n juwan
|
||||
kubectl get rolebinding -n juwan -l app=jwt-secret-reader
|
||||
```
|
||||
|
||||
### 第2步:更新 user-rpc 部署(依赖第1步)
|
||||
|
||||
已自动更新 `deploy/k8s/service/user/user-rpc.yaml`:
|
||||
|
||||
- ✅ 更新 `serviceAccountName` 从 `find-endpoints` → `user-rpc`
|
||||
- ✅ 添加环境变量 `JWT_SECRET_KEY` 从 Secret `jwt-secret` 读取
|
||||
|
||||
应用更新:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
```
|
||||
|
||||
验证部署:
|
||||
|
||||
```bash
|
||||
# 检查 ServiceAccount 已正确绑定
|
||||
kubectl get deployment user-rpc -n juwan -o yaml | grep -A 5 serviceAccountName
|
||||
|
||||
# 查看 Pod 是否以 user-rpc 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
|
||||
```
|
||||
|
||||
### 第3步:更新 Envoy 网关部署(依赖第1步)
|
||||
|
||||
已自动更新 `deploy/k8s/envoy/envoy.yaml`:
|
||||
|
||||
- ✅ 添加 `serviceAccountName: envoy-gateway` 到 Deployment spec
|
||||
|
||||
应用更新:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
验证部署:
|
||||
|
||||
```bash
|
||||
# 检查 ServiceAccount 已正确绑定
|
||||
kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName
|
||||
|
||||
# 检查 Pod 状态
|
||||
kubectl get pod -n juwan -l app=envoy-gateway
|
||||
```
|
||||
|
||||
### 第4步:启用 ETCD 加密(强烈推荐用于生产环境)
|
||||
|
||||
这是一个集群级别的配置,需要在 Kubernetes 控制平面节点上执行。
|
||||
|
||||
**前提条件:**
|
||||
- 具有 Kubernetes 集群管理员权限
|
||||
- 可以访问控制平面节点
|
||||
- 备份 ETCD 数据库
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. **生成加密密钥**
|
||||
```bash
|
||||
head -c 32 /dev/urandom | base64
|
||||
```
|
||||
记录输出的 Base64 密钥。
|
||||
|
||||
2. **创建加密配置文件**
|
||||
|
||||
在控制平面节点上,创建 `/etc/kubernetes/encryption-config.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: <BASE64_ENCODED_32_BYTE_KEY>
|
||||
- identity: {}
|
||||
```
|
||||
|
||||
替换 `<BASE64_ENCODED_32_BYTE_KEY>` 为第1步生成的密钥。
|
||||
|
||||
3. **修改 kube-apiserver 配置**
|
||||
|
||||
在控制平面节点上,编辑 `/etc/kubernetes/manifests/kube-apiserver.yaml`:
|
||||
|
||||
**添加参数:**
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
command:
|
||||
- kube-apiserver
|
||||
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
```
|
||||
|
||||
**添加卷挂载:**
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
volumeMounts:
|
||||
- name: encryption-config
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
- name: encryption-config
|
||||
hostPath:
|
||||
path: /etc/kubernetes
|
||||
type: DirectoryOrCreate
|
||||
```
|
||||
|
||||
4. **重启 kube-apiserver**
|
||||
|
||||
修改清单后,kubelet 会自动重启 kube-apiserver。检查状态:
|
||||
|
||||
```bash
|
||||
# 在控制平面节点
|
||||
kubectl get pods -n kube-system | grep kube-apiserver
|
||||
|
||||
# 监控重启过程
|
||||
kubectl logs -n kube-system -l component=kube-apiserver -f
|
||||
```
|
||||
|
||||
5. **验证加密是否启用**
|
||||
|
||||
创建一个新 Secret 并检查它在 ETCD 中是否加密:
|
||||
|
||||
```bash
|
||||
# 创建测试 Secret
|
||||
kubectl create secret generic test-secret -n juwan --from-literal=key=value
|
||||
|
||||
# 从 control plane 节点检查 ETCD 数据
|
||||
# 如果数据不可读并包含加密标记,说明加密已启用
|
||||
```
|
||||
|
||||
6. **保存加密密钥**
|
||||
|
||||
⚠️ **关键:将加密密钥安全地保存在离线存储中**
|
||||
- 密钥丢失后,无法解密 ETCD 中的数据
|
||||
- 无法恢复任何 Secrets
|
||||
- 建议用密码管理工具(如 HashiCorp Vault)或 HSM 存储密钥
|
||||
|
||||
### 第5步:验证整个系统
|
||||
|
||||
完整的验证清单:
|
||||
|
||||
```bash
|
||||
# 检查所有 Secrets 已创建
|
||||
kubectl get secret -n juwan
|
||||
kubectl get secret jwt-secret -n juwan -o jsonpath='{.data.secret-key}' | base64 -d
|
||||
|
||||
# 检查 ServiceAccounts 已创建
|
||||
kubectl get sa -n juwan
|
||||
kubectl describe sa user-rpc -n juwan
|
||||
kubectl describe sa envoy-gateway -n juwan
|
||||
|
||||
# 检查 RBAC 权限
|
||||
kubectl get role -n juwan
|
||||
kubectl get rolebinding -n juwan
|
||||
kubectl describe role jwt-secret-reader -n juwan
|
||||
|
||||
# 测试权限:user-rpc 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:user-rpc --resource-name=jwt-secret -n juwan
|
||||
|
||||
# 测试权限:envoy-gateway 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:envoy-gateway --resource-name=jwt-secret -n juwan
|
||||
|
||||
# 测试权限:其他 ServiceAccount 无法读取
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:other-service -n juwan
|
||||
|
||||
# 检查 Deployments 已正确配置
|
||||
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
|
||||
|
||||
# 检查 Pods 是否已启动并运行
|
||||
kubectl get pods -n juwan -l app=user-rpc
|
||||
kubectl get pods -n juwan -l app=envoy-gateway
|
||||
|
||||
# 查看 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
|
||||
```
|
||||
|
||||
## 监控和日志
|
||||
|
||||
### 查看 Pod 日志
|
||||
|
||||
```bash
|
||||
# user-rpc 日志
|
||||
kubectl logs -n juwan -l app=user-rpc -f --all-containers=true
|
||||
|
||||
# Envoy 日志
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
### 检查 Pod 事件
|
||||
|
||||
```bash
|
||||
# 查看 Pod 创建和启动事件
|
||||
kubectl describe pod -n juwan -l app=user-rpc
|
||||
kubectl describe pod -n juwan -l app=envoy-gateway
|
||||
```
|
||||
|
||||
### 权限问题排查
|
||||
|
||||
如果 Pod 无法读取 Secret:
|
||||
|
||||
```bash
|
||||
# 检查 Pod 使用的 ServiceAccount
|
||||
kubectl get pod POD_NAME -n juwan -o yaml | grep serviceAccountName
|
||||
|
||||
# 检查 RBAC 绑定
|
||||
kubectl get rolebinding -n juwan -o wide
|
||||
|
||||
# 检查 Role 权限定义
|
||||
kubectl get role jwt-secret-reader -n juwan -o yaml
|
||||
|
||||
# 尝试用 Pod 的身份读取 Secret(需要 kubectl-user-impersonate 或类似工具)
|
||||
kubectl get secret jwt-secret --as=system:serviceaccount:juwan:user-rpc -n juwan
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. 密钥轮换
|
||||
|
||||
周期性更换 JWT 秘钥(建议每季度):
|
||||
|
||||
```bash
|
||||
# 1. 生成新密钥
|
||||
NEW_KEY=$(head -c 32 /dev/urandom | base64)
|
||||
|
||||
# 2. 更新 Secret
|
||||
kubectl create secret generic jwt-secret \
|
||||
--from-literal=secret-key=$NEW_KEY \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 3. 重启 Pods 以加载新密钥(滚动更新)
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
|
||||
# 4. 验证新 Pods 已启动并运行
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 5. 已颁发的旧令牌将变为无效
|
||||
# 需要用户重新登录获取新令牌
|
||||
```
|
||||
|
||||
### 2. 审计和监控
|
||||
|
||||
在生产环境中启用 Kubernetes 审计日志来跟踪 Secret 访问:
|
||||
|
||||
```yaml
|
||||
# kube-apiserver 审计策略示例
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
# 记录 secret 资源的访问
|
||||
- level: RequestResponse
|
||||
verbs: ["get", "list", "watch"]
|
||||
resources: ["secrets"]
|
||||
# 记录所有认证失败
|
||||
- level: RequestResponse
|
||||
omitStages:
|
||||
- RequestReceived
|
||||
userGroups: ["system:unauthenticated"]
|
||||
- level: Metadata
|
||||
omitStages:
|
||||
- RequestReceived
|
||||
```
|
||||
|
||||
### 3. 网络策略
|
||||
|
||||
使用 NetworkPolicy 限制 Pods 之间的通信:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: jwt-secret-access
|
||||
namespace: juwan
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: jwt-secret-reader
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: user-rpc
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: envoy-gateway
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443 # API Server
|
||||
```
|
||||
|
||||
## 灾难恢复
|
||||
|
||||
### 备份 Secret 和 RBAC 配置
|
||||
|
||||
```bash
|
||||
# 备份 JWT Secret
|
||||
kubectl get secret jwt-secret -n juwan -o yaml > jwt-secret-backup.yaml
|
||||
|
||||
# 备份 RBAC 配置
|
||||
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
|
||||
|
||||
# 加密备份文件
|
||||
gpg --symmetric jwt-secret-backup.yaml
|
||||
```
|
||||
|
||||
### 恢复步骤
|
||||
|
||||
如果 Secret 被意外删除:
|
||||
|
||||
```bash
|
||||
# 从备份恢复
|
||||
kubectl apply -f jwt-secret-backup.yaml
|
||||
|
||||
# 重启 Pods 以重新加载 Secret
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: Pod 无法启动,显示 "failed to pull secret"
|
||||
|
||||
A: 检查:
|
||||
1. Secret 是否存在:`kubectl get secret jwt-secret -n juwan`
|
||||
2. ServiceAccount 是否绑定了 RBAC:`kubectl describe rolebinding -n juwan`
|
||||
3. Secret 名称和命名空间是否正确
|
||||
|
||||
### Q: 加密后如何验证 ETCD 中的数据已加密?
|
||||
|
||||
A: 从 control plane 节点:
|
||||
```bash
|
||||
# 直接读取 ETCD(如果配置了加密,数据应该不可读)
|
||||
sudo strings /var/lib/etcd/member/snap/db | grep -i secret
|
||||
```
|
||||
|
||||
### Q: 能否更改加密密钥而不重新创建 ETCD?
|
||||
|
||||
A: 可以,但流程复杂:
|
||||
1. 更新 encryption-config.yaml 中的新密钥
|
||||
2. 将新密钥添加到提供程序列表(保持旧密钥)
|
||||
3. 重启 kube-apiserver
|
||||
4. 触发重新加密:`kubectl get all --all-namespaces -o json | kubectl apply -f -`
|
||||
|
||||
### Q: 如何在 Minikube 中启用 ETCD 加密?
|
||||
|
||||
A: 参考 `ENCRYPTION.md` 中的 Minikube 特定说明部分。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `jwt-secret.yaml` - Secret 和 RBAC 配置
|
||||
- `ENCRYPTION.md` - ETCD 加密详细文档
|
||||
- `README.md` - 快速参考指南
|
||||
- `/deploy/k8s/service/user/user-rpc.yaml` - user-rpc Deployment 配置
|
||||
- `/deploy/k8s/envoy/envoy.yaml` - Envoy 网关 Deployment 配置
|
||||
|
||||
## 下一步
|
||||
|
||||
部署完成后:
|
||||
|
||||
1. **集成 JWT 验证到 RPC Handlers**
|
||||
- 实现 gRPC unary interceptor
|
||||
- 验证令牌有效性
|
||||
- 处理令牌刷新逻辑
|
||||
|
||||
2. **集成 JWT 验证到 Envoy**
|
||||
- 扩展 Lua filter 进行令牌验证
|
||||
- 返回 401(无效令牌)或 200(有效令牌)
|
||||
|
||||
3. **端到端测试**
|
||||
- 创建用户和登录
|
||||
- 生成和验证 JWT
|
||||
- 测试令牌刷新和撤销
|
||||
- 验证 ETCD 加密
|
||||
|
||||
4. **生产部署**
|
||||
- 启用审计日志
|
||||
- 配置密钥轮换计划
|
||||
- 建立备份和恢复流程
|
||||
- 监控 Secret 访问
|
||||
# JWT Secret + ETCD Encryption Deployment Guide
|
||||
|
||||
完整的 JWT 认证系统部署指南,包括密钥管理、RBAC 权限控制和 ETCD 加密。
|
||||
|
||||
## 部署顺序
|
||||
|
||||
### 第1步:创建 Secret 和 RBAC(必需)
|
||||
|
||||
创建 JWT 秘钥和服务账户权限:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
验证创建成功:
|
||||
|
||||
```bash
|
||||
# 检查 Secret
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
kubectl get secret jwt-secret -n juwan -o yaml
|
||||
|
||||
# 检查 ServiceAccounts
|
||||
kubectl get sa user-rpc -n juwan
|
||||
kubectl get sa envoy-gateway -n juwan
|
||||
|
||||
# 检查 RBAC 权限
|
||||
kubectl get role jwt-secret-reader -n juwan
|
||||
kubectl get rolebinding -n juwan -l app=jwt-secret-reader
|
||||
```
|
||||
|
||||
### 第2步:更新 user-rpc 部署(依赖第1步)
|
||||
|
||||
已自动更新 `deploy/k8s/service/user/user-rpc.yaml`:
|
||||
|
||||
- ✅ 更新 `serviceAccountName` 从 `find-endpoints` → `user-rpc`
|
||||
- ✅ 添加环境变量 `JWT_SECRET_KEY` 从 Secret `jwt-secret` 读取
|
||||
|
||||
应用更新:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
```
|
||||
|
||||
验证部署:
|
||||
|
||||
```bash
|
||||
# 检查 ServiceAccount 已正确绑定
|
||||
kubectl get deployment user-rpc -n juwan -o yaml | grep -A 5 serviceAccountName
|
||||
|
||||
# 查看 Pod 是否以 user-rpc 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
|
||||
```
|
||||
|
||||
### 第3步:更新 Envoy 网关部署(依赖第1步)
|
||||
|
||||
已自动更新 `deploy/k8s/envoy/envoy.yaml`:
|
||||
|
||||
- ✅ 添加 `serviceAccountName: envoy-gateway` 到 Deployment spec
|
||||
|
||||
应用更新:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
验证部署:
|
||||
|
||||
```bash
|
||||
# 检查 ServiceAccount 已正确绑定
|
||||
kubectl get deployment envoy-gateway -n juwan -o yaml | grep -A 2 serviceAccountName
|
||||
|
||||
# 检查 Pod 状态
|
||||
kubectl get pod -n juwan -l app=envoy-gateway
|
||||
```
|
||||
|
||||
### 第4步:启用 ETCD 加密(强烈推荐用于生产环境)
|
||||
|
||||
这是一个集群级别的配置,需要在 Kubernetes 控制平面节点上执行。
|
||||
|
||||
**前提条件:**
|
||||
- 具有 Kubernetes 集群管理员权限
|
||||
- 可以访问控制平面节点
|
||||
- 备份 ETCD 数据库
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. **生成加密密钥**
|
||||
```bash
|
||||
head -c 32 /dev/urandom | base64
|
||||
```
|
||||
记录输出的 Base64 密钥。
|
||||
|
||||
2. **创建加密配置文件**
|
||||
|
||||
在控制平面节点上,创建 `/etc/kubernetes/encryption-config.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: <BASE64_ENCODED_32_BYTE_KEY>
|
||||
- identity: {}
|
||||
```
|
||||
|
||||
替换 `<BASE64_ENCODED_32_BYTE_KEY>` 为第1步生成的密钥。
|
||||
|
||||
3. **修改 kube-apiserver 配置**
|
||||
|
||||
在控制平面节点上,编辑 `/etc/kubernetes/manifests/kube-apiserver.yaml`:
|
||||
|
||||
**添加参数:**
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
command:
|
||||
- kube-apiserver
|
||||
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
```
|
||||
|
||||
**添加卷挂载:**
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
volumeMounts:
|
||||
- name: encryption-config
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
- name: encryption-config
|
||||
hostPath:
|
||||
path: /etc/kubernetes
|
||||
type: DirectoryOrCreate
|
||||
```
|
||||
|
||||
4. **重启 kube-apiserver**
|
||||
|
||||
修改清单后,kubelet 会自动重启 kube-apiserver。检查状态:
|
||||
|
||||
```bash
|
||||
# 在控制平面节点
|
||||
kubectl get pods -n kube-system | grep kube-apiserver
|
||||
|
||||
# 监控重启过程
|
||||
kubectl logs -n kube-system -l component=kube-apiserver -f
|
||||
```
|
||||
|
||||
5. **验证加密是否启用**
|
||||
|
||||
创建一个新 Secret 并检查它在 ETCD 中是否加密:
|
||||
|
||||
```bash
|
||||
# 创建测试 Secret
|
||||
kubectl create secret generic test-secret -n juwan --from-literal=key=value
|
||||
|
||||
# 从 control plane 节点检查 ETCD 数据
|
||||
# 如果数据不可读并包含加密标记,说明加密已启用
|
||||
```
|
||||
|
||||
6. **保存加密密钥**
|
||||
|
||||
⚠️ **关键:将加密密钥安全地保存在离线存储中**
|
||||
- 密钥丢失后,无法解密 ETCD 中的数据
|
||||
- 无法恢复任何 Secrets
|
||||
- 建议用密码管理工具(如 HashiCorp Vault)或 HSM 存储密钥
|
||||
|
||||
### 第5步:验证整个系统
|
||||
|
||||
完整的验证清单:
|
||||
|
||||
```bash
|
||||
# 检查所有 Secrets 已创建
|
||||
kubectl get secret -n juwan
|
||||
kubectl get secret jwt-secret -n juwan -o jsonpath='{.data.secret-key}' | base64 -d
|
||||
|
||||
# 检查 ServiceAccounts 已创建
|
||||
kubectl get sa -n juwan
|
||||
kubectl describe sa user-rpc -n juwan
|
||||
kubectl describe sa envoy-gateway -n juwan
|
||||
|
||||
# 检查 RBAC 权限
|
||||
kubectl get role -n juwan
|
||||
kubectl get rolebinding -n juwan
|
||||
kubectl describe role jwt-secret-reader -n juwan
|
||||
|
||||
# 测试权限:user-rpc 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:user-rpc --resource-name=jwt-secret -n juwan
|
||||
|
||||
# 测试权限:envoy-gateway 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:envoy-gateway --resource-name=jwt-secret -n juwan
|
||||
|
||||
# 测试权限:其他 ServiceAccount 无法读取
|
||||
kubectl auth can-i get secrets --as=system:serviceaccount:juwan:other-service -n juwan
|
||||
|
||||
# 检查 Deployments 已正确配置
|
||||
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
|
||||
|
||||
# 检查 Pods 是否已启动并运行
|
||||
kubectl get pods -n juwan -l app=user-rpc
|
||||
kubectl get pods -n juwan -l app=envoy-gateway
|
||||
|
||||
# 查看 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
|
||||
```
|
||||
|
||||
## 监控和日志
|
||||
|
||||
### 查看 Pod 日志
|
||||
|
||||
```bash
|
||||
# user-rpc 日志
|
||||
kubectl logs -n juwan -l app=user-rpc -f --all-containers=true
|
||||
|
||||
# Envoy 日志
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
### 检查 Pod 事件
|
||||
|
||||
```bash
|
||||
# 查看 Pod 创建和启动事件
|
||||
kubectl describe pod -n juwan -l app=user-rpc
|
||||
kubectl describe pod -n juwan -l app=envoy-gateway
|
||||
```
|
||||
|
||||
### 权限问题排查
|
||||
|
||||
如果 Pod 无法读取 Secret:
|
||||
|
||||
```bash
|
||||
# 检查 Pod 使用的 ServiceAccount
|
||||
kubectl get pod POD_NAME -n juwan -o yaml | grep serviceAccountName
|
||||
|
||||
# 检查 RBAC 绑定
|
||||
kubectl get rolebinding -n juwan -o wide
|
||||
|
||||
# 检查 Role 权限定义
|
||||
kubectl get role jwt-secret-reader -n juwan -o yaml
|
||||
|
||||
# 尝试用 Pod 的身份读取 Secret(需要 kubectl-user-impersonate 或类似工具)
|
||||
kubectl get secret jwt-secret --as=system:serviceaccount:juwan:user-rpc -n juwan
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. 密钥轮换
|
||||
|
||||
周期性更换 JWT 秘钥(建议每季度):
|
||||
|
||||
```bash
|
||||
# 1. 生成新密钥
|
||||
NEW_KEY=$(head -c 32 /dev/urandom | base64)
|
||||
|
||||
# 2. 更新 Secret
|
||||
kubectl create secret generic jwt-secret \
|
||||
--from-literal=secret-key=$NEW_KEY \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 3. 重启 Pods 以加载新密钥(滚动更新)
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
|
||||
# 4. 验证新 Pods 已启动并运行
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 5. 已颁发的旧令牌将变为无效
|
||||
# 需要用户重新登录获取新令牌
|
||||
```
|
||||
|
||||
### 2. 审计和监控
|
||||
|
||||
在生产环境中启用 Kubernetes 审计日志来跟踪 Secret 访问:
|
||||
|
||||
```yaml
|
||||
# kube-apiserver 审计策略示例
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
# 记录 secret 资源的访问
|
||||
- level: RequestResponse
|
||||
verbs: ["get", "list", "watch"]
|
||||
resources: ["secrets"]
|
||||
# 记录所有认证失败
|
||||
- level: RequestResponse
|
||||
omitStages:
|
||||
- RequestReceived
|
||||
userGroups: ["system:unauthenticated"]
|
||||
- level: Metadata
|
||||
omitStages:
|
||||
- RequestReceived
|
||||
```
|
||||
|
||||
### 3. 网络策略
|
||||
|
||||
使用 NetworkPolicy 限制 Pods 之间的通信:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: jwt-secret-access
|
||||
namespace: juwan
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: jwt-secret-reader
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: user-rpc
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: envoy-gateway
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443 # API Server
|
||||
```
|
||||
|
||||
## 灾难恢复
|
||||
|
||||
### 备份 Secret 和 RBAC 配置
|
||||
|
||||
```bash
|
||||
# 备份 JWT Secret
|
||||
kubectl get secret jwt-secret -n juwan -o yaml > jwt-secret-backup.yaml
|
||||
|
||||
# 备份 RBAC 配置
|
||||
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
|
||||
|
||||
# 加密备份文件
|
||||
gpg --symmetric jwt-secret-backup.yaml
|
||||
```
|
||||
|
||||
### 恢复步骤
|
||||
|
||||
如果 Secret 被意外删除:
|
||||
|
||||
```bash
|
||||
# 从备份恢复
|
||||
kubectl apply -f jwt-secret-backup.yaml
|
||||
|
||||
# 重启 Pods 以重新加载 Secret
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: Pod 无法启动,显示 "failed to pull secret"
|
||||
|
||||
A: 检查:
|
||||
1. Secret 是否存在:`kubectl get secret jwt-secret -n juwan`
|
||||
2. ServiceAccount 是否绑定了 RBAC:`kubectl describe rolebinding -n juwan`
|
||||
3. Secret 名称和命名空间是否正确
|
||||
|
||||
### Q: 加密后如何验证 ETCD 中的数据已加密?
|
||||
|
||||
A: 从 control plane 节点:
|
||||
```bash
|
||||
# 直接读取 ETCD(如果配置了加密,数据应该不可读)
|
||||
sudo strings /var/lib/etcd/member/snap/db | grep -i secret
|
||||
```
|
||||
|
||||
### Q: 能否更改加密密钥而不重新创建 ETCD?
|
||||
|
||||
A: 可以,但流程复杂:
|
||||
1. 更新 encryption-config.yaml 中的新密钥
|
||||
2. 将新密钥添加到提供程序列表(保持旧密钥)
|
||||
3. 重启 kube-apiserver
|
||||
4. 触发重新加密:`kubectl get all --all-namespaces -o json | kubectl apply -f -`
|
||||
|
||||
### Q: 如何在 Minikube 中启用 ETCD 加密?
|
||||
|
||||
A: 参考 `ENCRYPTION.md` 中的 Minikube 特定说明部分。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `jwt-secret.yaml` - Secret 和 RBAC 配置
|
||||
- `ENCRYPTION.md` - ETCD 加密详细文档
|
||||
- `README.md` - 快速参考指南
|
||||
- `/deploy/k8s/service/user/user-rpc.yaml` - user-rpc Deployment 配置
|
||||
- `/deploy/k8s/envoy/envoy.yaml` - Envoy 网关 Deployment 配置
|
||||
|
||||
## 下一步
|
||||
|
||||
部署完成后:
|
||||
|
||||
1. **集成 JWT 验证到 RPC Handlers**
|
||||
- 实现 gRPC unary interceptor
|
||||
- 验证令牌有效性
|
||||
- 处理令牌刷新逻辑
|
||||
|
||||
2. **集成 JWT 验证到 Envoy**
|
||||
- 扩展 Lua filter 进行令牌验证
|
||||
- 返回 401(无效令牌)或 200(有效令牌)
|
||||
|
||||
3. **端到端测试**
|
||||
- 创建用户和登录
|
||||
- 生成和验证 JWT
|
||||
- 测试令牌刷新和撤销
|
||||
- 验证 ETCD 加密
|
||||
|
||||
4. **生产部署**
|
||||
- 启用审计日志
|
||||
- 配置密钥轮换计划
|
||||
- 建立备份和恢复流程
|
||||
- 监控 Secret 访问
|
||||
|
||||
+129
-129
@@ -1,129 +1,129 @@
|
||||
# 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.
|
||||
|
||||
## 1. Generate an Encryption Key
|
||||
|
||||
```bash
|
||||
# Generate a 32-byte base64-encoded key
|
||||
head -c 32 /dev/urandom | base64
|
||||
# Example output: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
|
||||
```
|
||||
|
||||
## 2. Create EncryptionConfiguration File
|
||||
|
||||
Create `/etc/kubernetes/encryption-config.yaml` on the control plane node:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
|
||||
- identity: {}
|
||||
```
|
||||
|
||||
## 3. Update kube-apiserver Static Pod Manifest
|
||||
|
||||
Edit `/etc/kubernetes/manifests/kube-apiserver.yaml` on the control plane node:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
command:
|
||||
- kube-apiserver
|
||||
# ... existing flags ...
|
||||
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
volumeMounts:
|
||||
- name: encryption-config
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: encryption-config
|
||||
hostPath:
|
||||
path: /etc/kubernetes
|
||||
type: DirectoryOrCreate
|
||||
```
|
||||
|
||||
## 4. Verify Encryption is Working
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
# Check if the secret is encrypted in etcd
|
||||
kubectl get secret test-secret -o yaml
|
||||
|
||||
# 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
|
||||
# The data should be encrypted (not human-readable)
|
||||
```
|
||||
|
||||
## 5. Important Notes
|
||||
|
||||
- **Backup your encryption key** in a secure location
|
||||
- **Never commit encryption keys** to version control
|
||||
- If you lose the key, all encrypted secrets will be unrecoverable
|
||||
- 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 ...`
|
||||
- Or use: `kubectl patch secret <name> -p '{}' --type=merge` (triggers re-encryption)
|
||||
|
||||
## 6. RBAC Configuration for JWT Secret
|
||||
|
||||
The `jwt-secret.yaml` includes RBAC rules that:
|
||||
- Create a `jwt-secret` Secret in the `juwan` namespace
|
||||
- Create ServiceAccounts for `user-rpc` and `envoy-gateway`
|
||||
- Create a Role `jwt-secret-reader` that allows reading only the `jwt-secret` Secret
|
||||
- Bind this Role to both ServiceAccounts via RoleBindings
|
||||
|
||||
This ensures:
|
||||
- Only `user-rpc` and `envoy-gateway` Pods can read the JWT secret
|
||||
- Other services and users cannot access the JWT secret
|
||||
- Least privilege access principle is enforced
|
||||
|
||||
## 7. Update Deployment to Use ServiceAccount
|
||||
|
||||
Make sure your Deployment references the ServiceAccount:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: user-rpc # This is important!
|
||||
containers:
|
||||
- name: user-rpc
|
||||
env:
|
||||
- name: JWT_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: jwt-secret
|
||||
key: secret-key
|
||||
```
|
||||
|
||||
## 8. For Minikube Users
|
||||
|
||||
If using Minikube, you can enable encryption with:
|
||||
|
||||
```bash
|
||||
minikube config set apiserver.encryption-provider-config /path/to/encryption-config.yaml
|
||||
minikube start
|
||||
```
|
||||
|
||||
Or manually edit the kube-apiserver manifest after starting Minikube:
|
||||
|
||||
```bash
|
||||
minikube ssh
|
||||
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
# Add the flags and volume mounts as shown above
|
||||
```
|
||||
# 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.
|
||||
|
||||
## 1. Generate an Encryption Key
|
||||
|
||||
```bash
|
||||
# Generate a 32-byte base64-encoded key
|
||||
head -c 32 /dev/urandom | base64
|
||||
# Example output: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
|
||||
```
|
||||
|
||||
## 2. Create EncryptionConfiguration File
|
||||
|
||||
Create `/etc/kubernetes/encryption-config.yaml` on the control plane node:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: sxFdbKYquCe3EbRWVV+pFe2lS8K8hbiv3V8ExQZ0fD4=
|
||||
- identity: {}
|
||||
```
|
||||
|
||||
## 3. Update kube-apiserver Static Pod Manifest
|
||||
|
||||
Edit `/etc/kubernetes/manifests/kube-apiserver.yaml` on the control plane node:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-apiserver
|
||||
command:
|
||||
- kube-apiserver
|
||||
# ... existing flags ...
|
||||
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
volumeMounts:
|
||||
- name: encryption-config
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: encryption-config
|
||||
hostPath:
|
||||
path: /etc/kubernetes
|
||||
type: DirectoryOrCreate
|
||||
```
|
||||
|
||||
## 4. Verify Encryption is Working
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
# Check if the secret is encrypted in etcd
|
||||
kubectl get secret test-secret -o yaml
|
||||
|
||||
# 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
|
||||
# The data should be encrypted (not human-readable)
|
||||
```
|
||||
|
||||
## 5. Important Notes
|
||||
|
||||
- **Backup your encryption key** in a secure location
|
||||
- **Never commit encryption keys** to version control
|
||||
- If you lose the key, all encrypted secrets will be unrecoverable
|
||||
- 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 ...`
|
||||
- Or use: `kubectl patch secret <name> -p '{}' --type=merge` (triggers re-encryption)
|
||||
|
||||
## 6. RBAC Configuration for JWT Secret
|
||||
|
||||
The `jwt-secret.yaml` includes RBAC rules that:
|
||||
- Create a `jwt-secret` Secret in the `juwan` namespace
|
||||
- Create ServiceAccounts for `user-rpc` and `envoy-gateway`
|
||||
- Create a Role `jwt-secret-reader` that allows reading only the `jwt-secret` Secret
|
||||
- Bind this Role to both ServiceAccounts via RoleBindings
|
||||
|
||||
This ensures:
|
||||
- Only `user-rpc` and `envoy-gateway` Pods can read the JWT secret
|
||||
- Other services and users cannot access the JWT secret
|
||||
- Least privilege access principle is enforced
|
||||
|
||||
## 7. Update Deployment to Use ServiceAccount
|
||||
|
||||
Make sure your Deployment references the ServiceAccount:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: user-rpc # This is important!
|
||||
containers:
|
||||
- name: user-rpc
|
||||
env:
|
||||
- name: JWT_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: jwt-secret
|
||||
key: secret-key
|
||||
```
|
||||
|
||||
## 8. For Minikube Users
|
||||
|
||||
If using Minikube, you can enable encryption with:
|
||||
|
||||
```bash
|
||||
minikube config set apiserver.encryption-provider-config /path/to/encryption-config.yaml
|
||||
minikube start
|
||||
```
|
||||
|
||||
Or manually edit the kube-apiserver manifest after starting Minikube:
|
||||
|
||||
```bash
|
||||
minikube ssh
|
||||
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
# Add the flags and volume mounts as shown above
|
||||
```
|
||||
|
||||
+415
-415
@@ -1,415 +1,415 @@
|
||||
# 部署流程图和时间线
|
||||
|
||||
## 部署架构流程图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ JWT 认证系统部署流程 │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 1: 前置检查 (5分钟) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ✓ Kubernetes 集群版本 >= 1.24 │
|
||||
│ ✓ kubectl 已配置,可访问集群 │
|
||||
│ ✓ juwan namespace 已存在 │
|
||||
│ ✓ redis-operator CRD 已安装 │
|
||||
│ ✓ 集群管理员权限(用于 ETCD 加密) │
|
||||
│ │
|
||||
│ 命令检查: │
|
||||
│ $ kubectl cluster-info │
|
||||
│ $ kubectl get ns juwan │
|
||||
│ $ kubectl get crd redisclusters.redis.redis.opstreelabs.in │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 2: 创建 Secret 和 RBAC (5分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml │
|
||||
│ │
|
||||
│ 创建的资源: │
|
||||
│ ✓ Secret: jwt-secret (包含 JWT 秘钥) │
|
||||
│ ✓ ServiceAccount: user-rpc │
|
||||
│ ✓ ServiceAccount: envoy-gateway │
|
||||
│ ✓ Role: jwt-secret-reader (只读权限) │
|
||||
│ ✓ RoleBinding: jwt-secret-reader-user-rpc │
|
||||
│ ✓ RoleBinding: jwt-secret-reader-envoy-gateway │
|
||||
│ │
|
||||
│ 验证: │
|
||||
│ $ kubectl get secret jwt-secret -n juwan │
|
||||
│ $ kubectl get sa -n juwan | grep -E "user-rpc|envoy" │
|
||||
│ $ kubectl get role -n juwan │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 3: 更新 Deployments (10分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Step 3a: 更新 user-rpc Deployment │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/service/user/user-rpc.yaml │
|
||||
│ │
|
||||
│ 变更: │
|
||||
│ - serviceAccountName: user-rpc (绑定权限) │
|
||||
│ - env.JWT_SECRET_KEY (从 Secret 挂载) │
|
||||
│ - 保持 Redis Cluster 配置 │
|
||||
│ │
|
||||
│ 等待 Pods 启动: │
|
||||
│ $ kubectl rollout status deployment/user-rpc -n juwan │
|
||||
│ │
|
||||
│ --- │
|
||||
│ │
|
||||
│ Step 3b: 更新 Envoy Gateway Deployment │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/envoy/envoy.yaml │
|
||||
│ │
|
||||
│ 变更: │
|
||||
│ - serviceAccountName: envoy-gateway (绑定权限) │
|
||||
│ - 保持 CSRF Lua 防护配置 │
|
||||
│ │
|
||||
│ 等待 Pods 启动: │
|
||||
│ $ kubectl rollout status deployment/envoy-gateway -n juwan │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 4: 验证部署 (15分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 检查清单: │
|
||||
│ │
|
||||
│ 1️⃣ Secret 和权限验证 │
|
||||
│ $ kubectl get secret jwt-secret -n juwan │
|
||||
│ $ kubectl get role jwt-secret-reader -n juwan │
|
||||
│ $ kubectl get rolebinding -n juwan | grep jwt-secret │
|
||||
│ │
|
||||
│ 2️⃣ 权限测试 │
|
||||
│ $ kubectl auth can-i get secrets \ │
|
||||
│ --as=system:serviceaccount:juwan:user-rpc \ │
|
||||
│ --resource-name=jwt-secret -n juwan │
|
||||
│ 预期: yes │
|
||||
│ │
|
||||
│ 3️⃣ Pods 运行状态 │
|
||||
│ $ kubectl get pods -n juwan -l app=user-rpc │
|
||||
│ $ kubectl get pods -n juwan -l app=envoy-gateway │
|
||||
│ 预期: 3 个 user-rpc Pods + 1 个 envoy-gateway Pod 都在 Running │
|
||||
│ │
|
||||
│ 4️⃣ 环境变量验证 │
|
||||
│ $ kubectl exec -it <user-rpc-pod> -n juwan -- env | grep JWT │
|
||||
│ 预期: JWT_SECRET_KEY=... │
|
||||
│ │
|
||||
│ 5️⃣ Redis 连接验证 │
|
||||
│ $ kubectl run redis-cli --image=redis:latest --rm -it \ │
|
||||
│ -- redis-cli -h user-redis.juwan:6379 PING │
|
||||
│ 预期: PONG │
|
||||
│ │
|
||||
│ 详见: VERIFICATION.md 第1-8部分 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─────────── 生产环境额外步骤 ──────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ Phase 5a: 应用集成 │ │ Phase 5b: 启用 ETCD 加密 (30分钟) │
|
||||
│ (2-3 小时) ⚠️ 推荐 │ │ ⚠️ 生产推荐,需集群管理员权限 │
|
||||
├──────────────────────────┤ ├─────────────────────────────────────┤
|
||||
│ │ │ │
|
||||
│ 实施内容: │ │ 前提条件: │
|
||||
│ ✓ gRPC Interceptor │ │ ✓ Control Plane 节点访问权限 │
|
||||
│ ✓ Login/Logout Handler │ │ ✓ ETCD 备份已创建 │
|
||||
│ ✓ JWT Middleware │ │ ✓ 加密密钥已生成 │
|
||||
│ ✓ Token Refresh Logic │ │ │
|
||||
│ ✓ Error Handling │ │ 步骤: │
|
||||
│ ✓ Unit Tests │ │ 1. 生成 32 字节密钥 │
|
||||
│ │ │ $ head -c 32 /dev/urandom | base64
|
||||
│ 参考: │ │ │
|
||||
│ INTEGRATION.md │ │ 2. 创建加密配置文件 │
|
||||
│ │ │ /etc/kubernetes/encryption-config.yaml
|
||||
│ 时间估计: │ │ │
|
||||
│ - gRPC 拦截器: 30分钟 │ │ 3. 修改 kube-apiserver manifest │
|
||||
│ - Handlers: 60分钟 │ │ 添加密钥路径和卷挂载 │
|
||||
│ - Middleware: 30分钟 │ │ │
|
||||
│ - 测试: 60分钟 │ │ 4. 重启 kube-apiserver │
|
||||
│ │ │ kubelet 自动重启 │
|
||||
│ │ │ │
|
||||
│ │ │ 5. 验证加密已启用 │
|
||||
│ │ │ kubectl create secret generic ... │
|
||||
│ │ │ 检查 ETCD 中的数据是否加密 │
|
||||
│ │ │ │
|
||||
│ │ │ 详见: ENCRYPTION.md (8个部分) │
|
||||
│ │ │ 验证: VERIFICATION.md 第9部分 │
|
||||
│ │ │ │
|
||||
└──────────────────────────┘ └─────────────────────────────────────┘
|
||||
│ │
|
||||
└───────────────────┬─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Phase 6: 完成 ✅ │
|
||||
├──────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 最终检查: │
|
||||
│ ✓ 所有 Pods 运行正常 │
|
||||
│ ✓ RBAC 权限已验证 │
|
||||
│ ✓ JWT 功能已集成 │
|
||||
│ ✓ 日志和监控已配置 │
|
||||
│ ✓ (可选) ETCD 加密已启用 │
|
||||
│ │
|
||||
│ 生产推荐: │
|
||||
│ ✓ 启用审计日志 │
|
||||
│ ✓ 配置密钥轮换计划(季度) │
|
||||
│ ✓ 备份密钥到安全位置 │
|
||||
│ ✓ 配置告警和监控 │
|
||||
│ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 时间估计和路径
|
||||
|
||||
```
|
||||
推荐部署路径
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 最快路径 (30 分钟) - 开发环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1: 前置检查 ⏱️ 5 分钟
|
||||
Phase 2: 创建 Secret 和 RBAC ⏱️ 5 分钟
|
||||
Phase 3: 更新 Deployments ⏱️ 10 分钟
|
||||
Phase 4: 验证部署 ⏱️ 10 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 30 分钟
|
||||
|
||||
|
||||
📊 默认路径 (75 分钟) - 测试环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1-4: 如上 ⏱️ 30 分钟
|
||||
Phase 5a: 应用集成(简单版) ⏱️ 45 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 75 分钟
|
||||
|
||||
|
||||
🏆 完整路径 (3.5-4 小时) - 生产环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1-4: 如上 ⏱️ 30 分钟
|
||||
Phase 5a: 应用集成(完整版) ⏱️ 2-3 小时
|
||||
Phase 5b: ETCD 加密配置 ⏱️ 30 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 3.5-4 小时
|
||||
|
||||
|
||||
📚 学习路径 (6-8 小时) - 从零开始理解
|
||||
────────────────────────────────────────────────────────────────
|
||||
文档阅读 (SUMMARY + DEPLOYMENT + INTEGRATION) ⏱️ 1-2 小时
|
||||
完整路径部署 ⏱️ 3.5-4 小时
|
||||
验证和测试 ⏱️ 1-2 小时
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 6-8 小时
|
||||
```
|
||||
|
||||
## 并行和串行步骤
|
||||
|
||||
```
|
||||
可以并行执行的任务
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 应用集成 (Phase 5a) │ ────┐
|
||||
├─────────────────────────────────┤ │ 可选,独立
|
||||
│ • gRPC interceptor │ │ 进行
|
||||
│ • REST middleware │ │
|
||||
│ • Handler 实现 │ │
|
||||
│ • 单元测试 │ │
|
||||
└─────────────────────────────────┘ │
|
||||
├─ 与 Phase 2-4 并行
|
||||
│
|
||||
┌─────────────────────────────────┐ │
|
||||
│ ETCD 加密 (Phase 5b) │ ────┘
|
||||
├─────────────────────────────────┤ │ 需要集群管理员
|
||||
│ • 生成密钥(可单独进行) │ │ 权限,在
|
||||
│ • 创建配置文件 │ │ Control Plane
|
||||
│ • 修改 kube-apiserver │ │ 节点执行
|
||||
│ • 重启 API server │ │
|
||||
└─────────────────────────────────┘────┘
|
||||
|
||||
|
||||
必须串行执行的步骤
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
Phase 1 → Phase 2 → Phase 3 → Phase 4 → (Phase 5a + Phase 5b)
|
||||
|
||||
↓ ↓ ↓ ↓
|
||||
前置检查 创建资源 部署应用 验证完整 可选扩展功能
|
||||
|
||||
• Phase 2 必须在 Phase 1 之后(需要 namespace)
|
||||
• Phase 3 必须在 Phase 2 之后(需要 RoleBinding)
|
||||
• Phase 4 必须在 Phase 3 之后(需要 Pods 启动)
|
||||
• Phase 5a/5b 可在 Phase 4 完成后并行进行
|
||||
```
|
||||
|
||||
## 关键时间点
|
||||
|
||||
```
|
||||
事件时间线
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
T+0 Phase 1: 验证前置条件
|
||||
└─ 预计 5 分钟
|
||||
|
||||
T+5 Phase 2: kubectl apply jwt-secret.yaml
|
||||
└─ 预计 1 分钟执行,5 分钟验证
|
||||
|
||||
T+11 Phase 3a: kubectl apply user-rpc.yaml
|
||||
└─ 3 个 Pods 启动(滚动更新)
|
||||
└─ 预计 ~3 分钟(取决于镜像拉取)
|
||||
|
||||
T+14 Phase 3b: kubectl apply envoy.yaml
|
||||
└─ 1 个 Pod 启动
|
||||
└─ 预计 ~2 分钟
|
||||
|
||||
T+16 Phase 4: 执行完整验证检查
|
||||
└─ 12 个验证部分,共 ~15 分钟
|
||||
|
||||
T+31 ✅ 基础部署完成
|
||||
|
||||
T+31 (可选) Phase 5a: 应用代码集成
|
||||
到 └─ 2-3 小时编码和测试
|
||||
T+211
|
||||
|
||||
T+31 (可选) Phase 5b: ETCD 加密
|
||||
到 └─ 30 分钟配置
|
||||
T+61
|
||||
|
||||
T+211 或 T+61 🎉 全部完成
|
||||
(取决于是否执行 Phase 5)
|
||||
```
|
||||
|
||||
## 推荐的部署顺序
|
||||
|
||||
### 对于 DevOps/SRE
|
||||
|
||||
```
|
||||
优先级顺序:
|
||||
|
||||
1️⃣ Phase 1-4 (核心部署) [必需]
|
||||
└─ 时间: 30 分钟
|
||||
|
||||
2️⃣ Phase 5b (ETCD 加密) [生产强烈推荐]
|
||||
└─ 时间: 30 分钟
|
||||
└─ 开始时间: T+16 之前 (与 Phase 4 并行)
|
||||
|
||||
3️⃣ 密钥备份和恢复计划 [重要]
|
||||
└─ 时间: 15 分钟
|
||||
└─ 参考: DEPLOYMENT.md 灾难恢复
|
||||
|
||||
4️⃣ Phase 5a 支持 [当开发完成时]
|
||||
└─ 协助开发团队集成 JWT
|
||||
└─ 参考: INTEGRATION.md
|
||||
```
|
||||
|
||||
### 对于应用开发者
|
||||
|
||||
```
|
||||
优先级顺序:
|
||||
|
||||
1️⃣ 了解系统架构 [了解背景]
|
||||
└─ 文档: SUMMARY.md
|
||||
└─ 时间: 10 分钟
|
||||
|
||||
2️⃣ Phase 5a: 代码集成 [并行进行]
|
||||
└─ 参考: INTEGRATION.md
|
||||
└─ 时间: 2-3 小时
|
||||
|
||||
3️⃣ 单元测试 [在开发中]
|
||||
└─ 参考: INTEGRATION.md 第 9 部分
|
||||
└─ 时间: 1 小时
|
||||
|
||||
4️⃣ 集成测试 [与运维协调]
|
||||
└─ 测试完整流程
|
||||
└─ 时间: 1-2 小时
|
||||
```
|
||||
|
||||
## 回滚应急步骤
|
||||
|
||||
如果部署失败,可以快速回滚:
|
||||
|
||||
```
|
||||
紧急回滚
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
如果 Phase 2 (Secret) 失败:
|
||||
→ kubectl delete secret jwt-secret -n juwan
|
||||
→ kubectl delete sa user-rpc envoy-gateway -n juwan
|
||||
→ kubectl delete role jwt-secret-reader -n juwan
|
||||
→ 修正配置后重新应用
|
||||
|
||||
如果 Phase 3 (Deployment) 失败:
|
||||
→ kubectl rollout undo deployment/user-rpc -n juwan
|
||||
→ kubectl rollout undo deployment/envoy-gateway -n juwan
|
||||
→ 或删除部署并使用稳定的旧版本重新部署
|
||||
|
||||
如果 Phase 5b (ETCD 加密) 失败:
|
||||
→ 从 kube-apiserver 清单中移除加密参数
|
||||
→ 重启 kube-apiserver
|
||||
→ 删除 /etc/kubernetes/encryption-config.yaml
|
||||
→ 从最近的 ETCD 备份恢复(如果需要)
|
||||
|
||||
注意: 备份是关键!每次重大操作前都应备份。
|
||||
```
|
||||
|
||||
## 部署检查点 (Go/No-Go)
|
||||
|
||||
```
|
||||
关键检查点
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ Checkpoint 1 (Phase 2 后)
|
||||
- Secret 已创建
|
||||
- ServiceAccounts 已创建
|
||||
- Go → 继续 Phase 3
|
||||
- No-Go → 检查 kubectl 权限
|
||||
|
||||
✅ Checkpoint 2 (Phase 3 后)
|
||||
- Pods 已启动 (Running)
|
||||
- No PodSchedulingFailure
|
||||
- Go → 继续 Phase 4
|
||||
- No-Go → 检查资源限制和镜像拉取
|
||||
|
||||
✅ Checkpoint 3 (Phase 4 权限测试)
|
||||
- user-rpc 可以读 jwt-secret
|
||||
- envoy-gateway 可以读 jwt-secret
|
||||
- 其他 SA 无法读取
|
||||
- Go → 继续 Phase 5
|
||||
- No-Go → 检查 RBAC 配置
|
||||
|
||||
✅ Checkpoint 4 (Phase 4 Redis 连接)
|
||||
- Redis Cluster 健康 (3/3 nodes)
|
||||
- PING 返回 PONG
|
||||
- Go → 继续应用集成
|
||||
- No-Go → 检查 Redis Pods 和网络
|
||||
|
||||
✅ Checkpoint 5 (Phase 5b - ETCD 加密)
|
||||
- 新创建的 Secret 在 ETCD 中已加密
|
||||
- Go → 生产就绪
|
||||
- No-Go → 检查 kube-apiserver 配置参数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用这个流程图
|
||||
|
||||
1. **首次部署** → 从 "推荐部署路径" 选择合适的版本
|
||||
2. **卡在某一步** → 查看对应的 Phase 描述和命令
|
||||
3. **估算时间** → 查看 "时间估计和路径" 部分
|
||||
4. **需要回滚** → 参考 "回滚应急步骤"
|
||||
5. **检查进度** → 使用 "部署检查点"
|
||||
|
||||
详细的部署步骤见:[DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||
# 部署流程图和时间线
|
||||
|
||||
## 部署架构流程图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ JWT 认证系统部署流程 │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 1: 前置检查 (5分钟) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ✓ Kubernetes 集群版本 >= 1.24 │
|
||||
│ ✓ kubectl 已配置,可访问集群 │
|
||||
│ ✓ juwan namespace 已存在 │
|
||||
│ ✓ redis-operator CRD 已安装 │
|
||||
│ ✓ 集群管理员权限(用于 ETCD 加密) │
|
||||
│ │
|
||||
│ 命令检查: │
|
||||
│ $ kubectl cluster-info │
|
||||
│ $ kubectl get ns juwan │
|
||||
│ $ kubectl get crd redisclusters.redis.redis.opstreelabs.in │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 2: 创建 Secret 和 RBAC (5分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml │
|
||||
│ │
|
||||
│ 创建的资源: │
|
||||
│ ✓ Secret: jwt-secret (包含 JWT 秘钥) │
|
||||
│ ✓ ServiceAccount: user-rpc │
|
||||
│ ✓ ServiceAccount: envoy-gateway │
|
||||
│ ✓ Role: jwt-secret-reader (只读权限) │
|
||||
│ ✓ RoleBinding: jwt-secret-reader-user-rpc │
|
||||
│ ✓ RoleBinding: jwt-secret-reader-envoy-gateway │
|
||||
│ │
|
||||
│ 验证: │
|
||||
│ $ kubectl get secret jwt-secret -n juwan │
|
||||
│ $ kubectl get sa -n juwan | grep -E "user-rpc|envoy" │
|
||||
│ $ kubectl get role -n juwan │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 3: 更新 Deployments (10分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Step 3a: 更新 user-rpc Deployment │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/service/user/user-rpc.yaml │
|
||||
│ │
|
||||
│ 变更: │
|
||||
│ - serviceAccountName: user-rpc (绑定权限) │
|
||||
│ - env.JWT_SECRET_KEY (从 Secret 挂载) │
|
||||
│ - 保持 Redis Cluster 配置 │
|
||||
│ │
|
||||
│ 等待 Pods 启动: │
|
||||
│ $ kubectl rollout status deployment/user-rpc -n juwan │
|
||||
│ │
|
||||
│ --- │
|
||||
│ │
|
||||
│ Step 3b: 更新 Envoy Gateway Deployment │
|
||||
│ 执行: │
|
||||
│ $ kubectl apply -f deploy/k8s/envoy/envoy.yaml │
|
||||
│ │
|
||||
│ 变更: │
|
||||
│ - serviceAccountName: envoy-gateway (绑定权限) │
|
||||
│ - 保持 CSRF Lua 防护配置 │
|
||||
│ │
|
||||
│ 等待 Pods 启动: │
|
||||
│ $ kubectl rollout status deployment/envoy-gateway -n juwan │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 4: 验证部署 (15分钟) ⚡ 必需 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 检查清单: │
|
||||
│ │
|
||||
│ 1️⃣ Secret 和权限验证 │
|
||||
│ $ kubectl get secret jwt-secret -n juwan │
|
||||
│ $ kubectl get role jwt-secret-reader -n juwan │
|
||||
│ $ kubectl get rolebinding -n juwan | grep jwt-secret │
|
||||
│ │
|
||||
│ 2️⃣ 权限测试 │
|
||||
│ $ kubectl auth can-i get secrets \ │
|
||||
│ --as=system:serviceaccount:juwan:user-rpc \ │
|
||||
│ --resource-name=jwt-secret -n juwan │
|
||||
│ 预期: yes │
|
||||
│ │
|
||||
│ 3️⃣ Pods 运行状态 │
|
||||
│ $ kubectl get pods -n juwan -l app=user-rpc │
|
||||
│ $ kubectl get pods -n juwan -l app=envoy-gateway │
|
||||
│ 预期: 3 个 user-rpc Pods + 1 个 envoy-gateway Pod 都在 Running │
|
||||
│ │
|
||||
│ 4️⃣ 环境变量验证 │
|
||||
│ $ kubectl exec -it <user-rpc-pod> -n juwan -- env | grep JWT │
|
||||
│ 预期: JWT_SECRET_KEY=... │
|
||||
│ │
|
||||
│ 5️⃣ Redis 连接验证 │
|
||||
│ $ kubectl run redis-cli --image=redis:latest --rm -it \ │
|
||||
│ -- redis-cli -h user-redis.juwan:6379 PING │
|
||||
│ 预期: PONG │
|
||||
│ │
|
||||
│ 详见: VERIFICATION.md 第1-8部分 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─────────── 生产环境额外步骤 ──────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ Phase 5a: 应用集成 │ │ Phase 5b: 启用 ETCD 加密 (30分钟) │
|
||||
│ (2-3 小时) ⚠️ 推荐 │ │ ⚠️ 生产推荐,需集群管理员权限 │
|
||||
├──────────────────────────┤ ├─────────────────────────────────────┤
|
||||
│ │ │ │
|
||||
│ 实施内容: │ │ 前提条件: │
|
||||
│ ✓ gRPC Interceptor │ │ ✓ Control Plane 节点访问权限 │
|
||||
│ ✓ Login/Logout Handler │ │ ✓ ETCD 备份已创建 │
|
||||
│ ✓ JWT Middleware │ │ ✓ 加密密钥已生成 │
|
||||
│ ✓ Token Refresh Logic │ │ │
|
||||
│ ✓ Error Handling │ │ 步骤: │
|
||||
│ ✓ Unit Tests │ │ 1. 生成 32 字节密钥 │
|
||||
│ │ │ $ head -c 32 /dev/urandom | base64
|
||||
│ 参考: │ │ │
|
||||
│ INTEGRATION.md │ │ 2. 创建加密配置文件 │
|
||||
│ │ │ /etc/kubernetes/encryption-config.yaml
|
||||
│ 时间估计: │ │ │
|
||||
│ - gRPC 拦截器: 30分钟 │ │ 3. 修改 kube-apiserver manifest │
|
||||
│ - Handlers: 60分钟 │ │ 添加密钥路径和卷挂载 │
|
||||
│ - Middleware: 30分钟 │ │ │
|
||||
│ - 测试: 60分钟 │ │ 4. 重启 kube-apiserver │
|
||||
│ │ │ kubelet 自动重启 │
|
||||
│ │ │ │
|
||||
│ │ │ 5. 验证加密已启用 │
|
||||
│ │ │ kubectl create secret generic ... │
|
||||
│ │ │ 检查 ETCD 中的数据是否加密 │
|
||||
│ │ │ │
|
||||
│ │ │ 详见: ENCRYPTION.md (8个部分) │
|
||||
│ │ │ 验证: VERIFICATION.md 第9部分 │
|
||||
│ │ │ │
|
||||
└──────────────────────────┘ └─────────────────────────────────────┘
|
||||
│ │
|
||||
└───────────────────┬─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Phase 6: 完成 ✅ │
|
||||
├──────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 最终检查: │
|
||||
│ ✓ 所有 Pods 运行正常 │
|
||||
│ ✓ RBAC 权限已验证 │
|
||||
│ ✓ JWT 功能已集成 │
|
||||
│ ✓ 日志和监控已配置 │
|
||||
│ ✓ (可选) ETCD 加密已启用 │
|
||||
│ │
|
||||
│ 生产推荐: │
|
||||
│ ✓ 启用审计日志 │
|
||||
│ ✓ 配置密钥轮换计划(季度) │
|
||||
│ ✓ 备份密钥到安全位置 │
|
||||
│ ✓ 配置告警和监控 │
|
||||
│ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 时间估计和路径
|
||||
|
||||
```
|
||||
推荐部署路径
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 最快路径 (30 分钟) - 开发环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1: 前置检查 ⏱️ 5 分钟
|
||||
Phase 2: 创建 Secret 和 RBAC ⏱️ 5 分钟
|
||||
Phase 3: 更新 Deployments ⏱️ 10 分钟
|
||||
Phase 4: 验证部署 ⏱️ 10 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 30 分钟
|
||||
|
||||
|
||||
📊 默认路径 (75 分钟) - 测试环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1-4: 如上 ⏱️ 30 分钟
|
||||
Phase 5a: 应用集成(简单版) ⏱️ 45 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 75 分钟
|
||||
|
||||
|
||||
🏆 完整路径 (3.5-4 小时) - 生产环境
|
||||
────────────────────────────────────────────────────────────────
|
||||
Phase 1-4: 如上 ⏱️ 30 分钟
|
||||
Phase 5a: 应用集成(完整版) ⏱️ 2-3 小时
|
||||
Phase 5b: ETCD 加密配置 ⏱️ 30 分钟
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 3.5-4 小时
|
||||
|
||||
|
||||
📚 学习路径 (6-8 小时) - 从零开始理解
|
||||
────────────────────────────────────────────────────────────────
|
||||
文档阅读 (SUMMARY + DEPLOYMENT + INTEGRATION) ⏱️ 1-2 小时
|
||||
完整路径部署 ⏱️ 3.5-4 小时
|
||||
验证和测试 ⏱️ 1-2 小时
|
||||
────────────────────────────────────────────────────────────────
|
||||
总计: 6-8 小时
|
||||
```
|
||||
|
||||
## 并行和串行步骤
|
||||
|
||||
```
|
||||
可以并行执行的任务
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 应用集成 (Phase 5a) │ ────┐
|
||||
├─────────────────────────────────┤ │ 可选,独立
|
||||
│ • gRPC interceptor │ │ 进行
|
||||
│ • REST middleware │ │
|
||||
│ • Handler 实现 │ │
|
||||
│ • 单元测试 │ │
|
||||
└─────────────────────────────────┘ │
|
||||
├─ 与 Phase 2-4 并行
|
||||
│
|
||||
┌─────────────────────────────────┐ │
|
||||
│ ETCD 加密 (Phase 5b) │ ────┘
|
||||
├─────────────────────────────────┤ │ 需要集群管理员
|
||||
│ • 生成密钥(可单独进行) │ │ 权限,在
|
||||
│ • 创建配置文件 │ │ Control Plane
|
||||
│ • 修改 kube-apiserver │ │ 节点执行
|
||||
│ • 重启 API server │ │
|
||||
└─────────────────────────────────┘────┘
|
||||
|
||||
|
||||
必须串行执行的步骤
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
Phase 1 → Phase 2 → Phase 3 → Phase 4 → (Phase 5a + Phase 5b)
|
||||
|
||||
↓ ↓ ↓ ↓
|
||||
前置检查 创建资源 部署应用 验证完整 可选扩展功能
|
||||
|
||||
• Phase 2 必须在 Phase 1 之后(需要 namespace)
|
||||
• Phase 3 必须在 Phase 2 之后(需要 RoleBinding)
|
||||
• Phase 4 必须在 Phase 3 之后(需要 Pods 启动)
|
||||
• Phase 5a/5b 可在 Phase 4 完成后并行进行
|
||||
```
|
||||
|
||||
## 关键时间点
|
||||
|
||||
```
|
||||
事件时间线
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
T+0 Phase 1: 验证前置条件
|
||||
└─ 预计 5 分钟
|
||||
|
||||
T+5 Phase 2: kubectl apply jwt-secret.yaml
|
||||
└─ 预计 1 分钟执行,5 分钟验证
|
||||
|
||||
T+11 Phase 3a: kubectl apply user-rpc.yaml
|
||||
└─ 3 个 Pods 启动(滚动更新)
|
||||
└─ 预计 ~3 分钟(取决于镜像拉取)
|
||||
|
||||
T+14 Phase 3b: kubectl apply envoy.yaml
|
||||
└─ 1 个 Pod 启动
|
||||
└─ 预计 ~2 分钟
|
||||
|
||||
T+16 Phase 4: 执行完整验证检查
|
||||
└─ 12 个验证部分,共 ~15 分钟
|
||||
|
||||
T+31 ✅ 基础部署完成
|
||||
|
||||
T+31 (可选) Phase 5a: 应用代码集成
|
||||
到 └─ 2-3 小时编码和测试
|
||||
T+211
|
||||
|
||||
T+31 (可选) Phase 5b: ETCD 加密
|
||||
到 └─ 30 分钟配置
|
||||
T+61
|
||||
|
||||
T+211 或 T+61 🎉 全部完成
|
||||
(取决于是否执行 Phase 5)
|
||||
```
|
||||
|
||||
## 推荐的部署顺序
|
||||
|
||||
### 对于 DevOps/SRE
|
||||
|
||||
```
|
||||
优先级顺序:
|
||||
|
||||
1️⃣ Phase 1-4 (核心部署) [必需]
|
||||
└─ 时间: 30 分钟
|
||||
|
||||
2️⃣ Phase 5b (ETCD 加密) [生产强烈推荐]
|
||||
└─ 时间: 30 分钟
|
||||
└─ 开始时间: T+16 之前 (与 Phase 4 并行)
|
||||
|
||||
3️⃣ 密钥备份和恢复计划 [重要]
|
||||
└─ 时间: 15 分钟
|
||||
└─ 参考: DEPLOYMENT.md 灾难恢复
|
||||
|
||||
4️⃣ Phase 5a 支持 [当开发完成时]
|
||||
└─ 协助开发团队集成 JWT
|
||||
└─ 参考: INTEGRATION.md
|
||||
```
|
||||
|
||||
### 对于应用开发者
|
||||
|
||||
```
|
||||
优先级顺序:
|
||||
|
||||
1️⃣ 了解系统架构 [了解背景]
|
||||
└─ 文档: SUMMARY.md
|
||||
└─ 时间: 10 分钟
|
||||
|
||||
2️⃣ Phase 5a: 代码集成 [并行进行]
|
||||
└─ 参考: INTEGRATION.md
|
||||
└─ 时间: 2-3 小时
|
||||
|
||||
3️⃣ 单元测试 [在开发中]
|
||||
└─ 参考: INTEGRATION.md 第 9 部分
|
||||
└─ 时间: 1 小时
|
||||
|
||||
4️⃣ 集成测试 [与运维协调]
|
||||
└─ 测试完整流程
|
||||
└─ 时间: 1-2 小时
|
||||
```
|
||||
|
||||
## 回滚应急步骤
|
||||
|
||||
如果部署失败,可以快速回滚:
|
||||
|
||||
```
|
||||
紧急回滚
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
如果 Phase 2 (Secret) 失败:
|
||||
→ kubectl delete secret jwt-secret -n juwan
|
||||
→ kubectl delete sa user-rpc envoy-gateway -n juwan
|
||||
→ kubectl delete role jwt-secret-reader -n juwan
|
||||
→ 修正配置后重新应用
|
||||
|
||||
如果 Phase 3 (Deployment) 失败:
|
||||
→ kubectl rollout undo deployment/user-rpc -n juwan
|
||||
→ kubectl rollout undo deployment/envoy-gateway -n juwan
|
||||
→ 或删除部署并使用稳定的旧版本重新部署
|
||||
|
||||
如果 Phase 5b (ETCD 加密) 失败:
|
||||
→ 从 kube-apiserver 清单中移除加密参数
|
||||
→ 重启 kube-apiserver
|
||||
→ 删除 /etc/kubernetes/encryption-config.yaml
|
||||
→ 从最近的 ETCD 备份恢复(如果需要)
|
||||
|
||||
注意: 备份是关键!每次重大操作前都应备份。
|
||||
```
|
||||
|
||||
## 部署检查点 (Go/No-Go)
|
||||
|
||||
```
|
||||
关键检查点
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ Checkpoint 1 (Phase 2 后)
|
||||
- Secret 已创建
|
||||
- ServiceAccounts 已创建
|
||||
- Go → 继续 Phase 3
|
||||
- No-Go → 检查 kubectl 权限
|
||||
|
||||
✅ Checkpoint 2 (Phase 3 后)
|
||||
- Pods 已启动 (Running)
|
||||
- No PodSchedulingFailure
|
||||
- Go → 继续 Phase 4
|
||||
- No-Go → 检查资源限制和镜像拉取
|
||||
|
||||
✅ Checkpoint 3 (Phase 4 权限测试)
|
||||
- user-rpc 可以读 jwt-secret
|
||||
- envoy-gateway 可以读 jwt-secret
|
||||
- 其他 SA 无法读取
|
||||
- Go → 继续 Phase 5
|
||||
- No-Go → 检查 RBAC 配置
|
||||
|
||||
✅ Checkpoint 4 (Phase 4 Redis 连接)
|
||||
- Redis Cluster 健康 (3/3 nodes)
|
||||
- PING 返回 PONG
|
||||
- Go → 继续应用集成
|
||||
- No-Go → 检查 Redis Pods 和网络
|
||||
|
||||
✅ Checkpoint 5 (Phase 5b - ETCD 加密)
|
||||
- 新创建的 Secret 在 ETCD 中已加密
|
||||
- Go → 生产就绪
|
||||
- No-Go → 检查 kube-apiserver 配置参数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用这个流程图
|
||||
|
||||
1. **首次部署** → 从 "推荐部署路径" 选择合适的版本
|
||||
2. **卡在某一步** → 查看对应的 Phase 描述和命令
|
||||
3. **估算时间** → 查看 "时间估计和路径" 部分
|
||||
4. **需要回滚** → 参考 "回滚应急步骤"
|
||||
5. **检查进度** → 使用 "部署检查点"
|
||||
|
||||
详细的部署步骤见:[DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||
|
||||
+399
-399
@@ -1,399 +1,399 @@
|
||||
# JWT + ETCD 加密系统文档索引
|
||||
|
||||
## 📚 文档完整导航
|
||||
|
||||
### 快速入门 (5-15分钟)
|
||||
|
||||
**推荐路径:** 从上到下顺序阅读
|
||||
|
||||
1. **[SUMMARY.md](./SUMMARY.md)** ⭐ 从这里开始
|
||||
- 📋 项目概览和架构图
|
||||
- 🎯 核心特性一览
|
||||
- ✅ 生产就绪检查清单
|
||||
- 🚀 下一步行动计划
|
||||
|
||||
2. **[README.md](./README.md)**
|
||||
- 🏃 4个快速部署步骤
|
||||
- 🔐 安全考虑事项
|
||||
- 🔄 密钥轮换程序
|
||||
- 🆘 故障排查
|
||||
|
||||
3. **[QUICK_REFERENCE.md](./QUICK_REFERENCE.md)**
|
||||
- ⚡ 一页速查表
|
||||
- 📝 常见命令复制粘贴
|
||||
- 🗺️ 文档地图
|
||||
- 🎓 关键参数速知
|
||||
|
||||
### 部署实施 (30-60分钟)
|
||||
|
||||
4. **[DEPLOYMENT.md](./DEPLOYMENT.md)** - 最详细的部署指南
|
||||
- 📦 第1步:创建 Secret 和 RBAC(必需)
|
||||
- 🔄 第2步:更新 user-rpc Deployment
|
||||
- 🌐 第3步:更新 Envoy Gateway Deployment
|
||||
- 🔐 第4步:启用 ETCD 加密(生产推荐)
|
||||
- ✔️ 第5步:验证整个系统
|
||||
- 📊 监控和日志配置
|
||||
- 🛠️ 安全最佳实践
|
||||
- 🆘 故障排查指南
|
||||
- 💾 灾难恢复流程
|
||||
|
||||
### 验证和监控 (20-30分钟)
|
||||
|
||||
5. **[VERIFICATION.md](./VERIFICATION.md)** - 完整验证清单
|
||||
|
||||
**12个验证部分:**
|
||||
|
||||
| 部分 | 用途 | 时间 |
|
||||
|-----|------|------|
|
||||
| 第1部分 | Secret/RBAC 基础验证 | 2分钟 |
|
||||
| 第2部分 | 权限测试(allow/deny) | 3分钟 |
|
||||
| 第3部分 | Deployment 配置检查 | 2分钟 |
|
||||
| 第4部分 | Redis 连接测试 | 2分钟 |
|
||||
| 第5部分 | 应用启动日志 | 3分钟 |
|
||||
| 第6部分 | 网络和服务发现 | 3分钟 |
|
||||
| 第7部分 | Prometheus 指标 | 3分钟 |
|
||||
| 第8部分 | Loki 日志聚合 | 2分钟 |
|
||||
| 第9部分 | ETCD 加密验证 | 5分钟 |
|
||||
| 第10部分 | JWT 功能测试 | 10分钟 |
|
||||
| 第11部分 | 故障排查诊断 | 5分钟 |
|
||||
| 第12部分 | 清理和总结 | 2分钟 |
|
||||
|
||||
### 高级话题
|
||||
|
||||
6. **[ENCRYPTION.md](./ENCRYPTION.md)** - ETCD 加密完整指南
|
||||
- 🔑 第1部分:密钥生成
|
||||
- 📋 第2部分:配置格式
|
||||
- 🔧 第3部分:kube-apiserver 修改
|
||||
- ✅第4部分:验证加密
|
||||
- ⚠️ 第5部分:关键警告(数据不可恢复)
|
||||
- 🔐 第6部分:RBAC 解释
|
||||
- 📦 第7部分:Deployment 示例
|
||||
- 🍎 第8部分:Minikube 特定说明
|
||||
|
||||
7. **[INTEGRATION.md](../api/INTEGRATION.md)** - 代码集成指南
|
||||
- 🔗 第1部分:gRPC Unary Interceptor
|
||||
- 🔗 第2部分:gRPC Stream Interceptor
|
||||
- 👤 第3部分:登录 Handler 实现
|
||||
- 🔐 第4部分:受保护 Handler 中的声明提取
|
||||
- 🔄 第5部分:令牌刷新端点
|
||||
- 🚪 第6部分:登出处理
|
||||
- 🛣️ 第7部分:REST Routes 配置
|
||||
- 🔎 第8部分:错误处理最佳实践
|
||||
- 🧪 第9部分:单元测试示例
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件结构详解
|
||||
|
||||
```
|
||||
deploy/k8s/
|
||||
├── secrets/
|
||||
│ ├── jwt-secret.yaml ✅ Kubernetes 清单文件
|
||||
│ │ ├── Secret: jwt-secret (JWT 秘钥数据)
|
||||
│ │ ├── ServiceAccount: user-rpc
|
||||
│ │ ├── ServiceAccount: envoy-gateway
|
||||
│ │ ├── Role: jwt-secret-reader
|
||||
│ │ ├── RoleBinding: jwt-secret-reader-user-rpc
|
||||
│ │ └── RoleBinding: jwt-secret-reader-envoy-gateway
|
||||
│ │
|
||||
│ ├── README.md 📖 快速参考指南(5分钟入门)
|
||||
│ ├── SUMMARY.md 📊 系统概览(10分钟了解全貌)
|
||||
│ ├── QUICK_REFERENCE.md ⚡ 速查表(查找命令和参数)
|
||||
│ ├── DEPLOYMENT.md 📦 详细部署指南(60分钟完整部署)
|
||||
│ ├── ENCRYPTION.md 🔐 ETCD 加密指南(Control Plane 配置)
|
||||
│ ├── VERIFICATION.md ✅ 验证清单(部署后验证)
|
||||
│ └── INDEX.md 🗺️ 本文件(文档导航)
|
||||
│
|
||||
└── envoy/
|
||||
│ └── envoy.yaml ✅ Envoy 网关配置
|
||||
│ └── 已更新: serviceAccountName: envoy-gateway
|
||||
│
|
||||
service/user/
|
||||
├── user-api.yaml ✅ user-api Service
|
||||
├── user-rpc.yaml ✅ user-rpc Deployment(已更新)
|
||||
│ ├── serviceAccountName: user-rpc (已更新)
|
||||
│ ├── JWT_SECRET_KEY env var (已更新)
|
||||
│ └── Redis Cluster configuration
|
||||
└── ...
|
||||
|
||||
app/users/
|
||||
├── api/
|
||||
│ └── INTEGRATION.md 📝 REST/gRPC 集成指南
|
||||
│
|
||||
└── rpc/
|
||||
├── internal/utils/jwt.go ✅ JwtManager 实现(已存在)
|
||||
├── internal/config/config.go ✅ JWT 配置(已存在)
|
||||
├── internal/svc/
|
||||
│ └── serviceContext.go ✅ 依赖注入(已存在)
|
||||
└── etc/pb.yaml ✅ 运行时配置(已存在)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 按场景查找文档
|
||||
|
||||
### 场景 1:我想快速了解这个系统是什么
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [SUMMARY.md](./SUMMARY.md) - 项目概览(5分钟)
|
||||
2. [SUMMARY.md](./SUMMARY.md) 中的架构图和特性说明
|
||||
|
||||
**关键信息:**
|
||||
- JWT 令牌系统 + Redis 存储 + RBAC 权限 + ETCD 加密
|
||||
- 支持 7 天有效期、30 天可刷新
|
||||
- Envoy 网关 CSRF 防护
|
||||
|
||||
---
|
||||
|
||||
### 场景 2:我想立即部署到 Kubernetes
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [README.md](./README.md) - 快速参考(2分钟)
|
||||
2. [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - 复制粘贴命令(3分钟)
|
||||
3. 运行部署命令(5分钟)
|
||||
4. [VERIFICATION.md](./VERIFICATION.md) 第1-7部分 - 验证(10分钟)
|
||||
|
||||
**快速命令:**
|
||||
```bash
|
||||
# Copy from QUICK_REFERENCE.md "部署命令" 部分
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 场景 3:部署后验证一切正常
|
||||
|
||||
**推荐阅读:**
|
||||
- [VERIFICATION.md](./VERIFICATION.md) - 12部分完整验证清单
|
||||
- 逐部分执行验证命令
|
||||
|
||||
**预计时间:** 30-40分钟
|
||||
|
||||
**验证触发点:**
|
||||
- ✅ Secrets 和 RBAC 已创建
|
||||
- ✅ Pods 已启动运行
|
||||
- ✅ 权限验证通过
|
||||
- ✅ Redis 连接成功
|
||||
|
||||
---
|
||||
|
||||
### 场景 4:启用 ETCD 加密(生产推荐)
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [ENCRYPTION.md](./ENCRYPTION.md) - 完整加密指南
|
||||
2. 按照 8 个步骤逐一执行
|
||||
3. [VERIFICATION.md](./VERIFICATION.md) 第9部分 - 加密验证
|
||||
|
||||
**需要的权限:**
|
||||
- Control Plane 节点的 root/sudo 权限
|
||||
- Kubernetes 集群管理员权限
|
||||
|
||||
**预计时间:** 15-20分钟
|
||||
|
||||
---
|
||||
|
||||
### 场景 5:集成 JWT 到我的应用代码中
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [INTEGRATION.md](../api/INTEGRATION.md) 第1-2部分 - gRPC 拦截器
|
||||
2. 第3-4部分 - 登录和受保护 Handlers
|
||||
3. 第7-8部分 - REST API 中间件
|
||||
4. 第9部分 - 单元测试
|
||||
|
||||
**需要实现:**
|
||||
- ✅ gRPC Unary/Stream Interceptors
|
||||
- ✅ 登录/登出端点
|
||||
- ✅ JWT Middleware for REST
|
||||
- ✅ 错误处理
|
||||
|
||||
**预计时间:** 2-3 小时
|
||||
|
||||
---
|
||||
|
||||
### 场景 6:部署后遇到问题
|
||||
|
||||
**根据错误类型选择:**
|
||||
|
||||
| 错误类型 | 查看文档 |
|
||||
|---------|--------|
|
||||
| Pod 无法启动 | [VERIFICATION.md](./VERIFICATION.md) 第11部分 |
|
||||
| 权限被拒绝 | [VERIFICATION.md](./VERIFICATION.md) 第2部分 + [README.md](./README.md) 故障排查 |
|
||||
| Redis 连接失败 | [VERIFICATION.md](./VERIFICATION.md) 第4部分 |
|
||||
| ETCD 加密失败 | [ENCRYPTION.md](./ENCRYPTION.md) 第5-6部分 |
|
||||
| 配置不清楚 | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 配置文件位置 |
|
||||
|
||||
---
|
||||
|
||||
### 场景 7:定期维护任务
|
||||
|
||||
#### 任务:轮换 JWT 秘钥
|
||||
|
||||
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 安全最佳实践 > 密钥轮换
|
||||
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 密钥轮换步骤
|
||||
|
||||
**频率:** 季度(3个月)
|
||||
|
||||
#### 任务:轮换 ETCD 加密密钥
|
||||
|
||||
**阅读:** [ENCRYPTION.md](./ENCRYPTION.md) 第5部分
|
||||
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) ETCD 加密系统
|
||||
|
||||
**频率:** 年度(12个月)
|
||||
|
||||
#### 任务:备份密钥
|
||||
|
||||
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 灾难恢复
|
||||
**或:** [ENCRYPTION.md](./ENCRYPTION.md) 关键警告
|
||||
|
||||
**频率:** 立即 + 每次轮换后
|
||||
|
||||
---
|
||||
|
||||
## 📊 文档深度对比
|
||||
|
||||
| 文档 | 深度 | 丰富度 | 代码 | 适合角色 |
|
||||
|-----|------|--------|------|---------|
|
||||
| README | 浅 | 概览 | - | PM/初学者 |
|
||||
| SUMMARY | 浅 | 概览 | - | 决策者 |
|
||||
| QUICK_REFERENCE | 中 | 速查 | 命令 | DevOps/SRE |
|
||||
| DEPLOYMENT | 深 | 详细 | 示例 | DevOps/运维 |
|
||||
| VERIFICATION | 深 | 详细 | 脚本 | QA/DevOps |
|
||||
| ENCRYPTION | 非常深 | 极详细 | YAML | 安全/运维 |
|
||||
| INTEGRATION | 非常深 | 代码级 | 完整 | 开发者 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 学习路径建议
|
||||
|
||||
### 对于 DevOps/SRE
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. DEPLOYMENT.md (30 分钟)
|
||||
3. VERIFICATION.md (30 分钟)
|
||||
4. ENCRYPTION.md (20 分钟)
|
||||
5. 实践部署 (60 分钟)
|
||||
|
||||
**总计:** ~3 小时
|
||||
|
||||
### 对于应用开发者
|
||||
|
||||
1. SUMMARY.md > "集成点" 部分 (5 分钟)
|
||||
2. INTEGRATION.md (60 分钟)
|
||||
3. QUICK_REFERENCE.md > "JWT Manager API" (10 分钟)
|
||||
4. 代码实现 (2-3 小时)
|
||||
5. 单元测试 (INTEGRATION.md 第9部分)
|
||||
|
||||
**总计:** ~4 小时
|
||||
|
||||
### 对于安全/合规人员
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. ENCRYPTION.md (30 分钟)
|
||||
3. DEPLOYMENT.md > 安全最佳实践 (15 分钟)
|
||||
4. VERIFICATION.md 第9部分 (10 分钟)
|
||||
|
||||
**总计:** ~1 小时
|
||||
|
||||
### 对于项目经理
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. DEPLOYMENT.md > "部署状态示意图" (5 分钟)
|
||||
3. DEPLOYMENT.md > "快速部署" (2 分钟)
|
||||
|
||||
**总计:** ~15 分钟
|
||||
|
||||
---
|
||||
|
||||
## 🎓 学习成果预期
|
||||
|
||||
### 完成后,您将能够:
|
||||
|
||||
✅ 在 Kubernetes 中部署 JWT 认证系统
|
||||
✅ 配置 RBAC 权限控制
|
||||
✅ 启用 ETCD 加密保护敏感数据
|
||||
✅ 在 Go-zero 应用中集成 JWT
|
||||
✅ 实现令牌刷新和撤销
|
||||
✅ 诊断和排查常见问题
|
||||
✅ 执行密钥轮换和灾难恢复
|
||||
|
||||
---
|
||||
|
||||
## 🆘 求助指南
|
||||
|
||||
### 第一步:找到相关文档
|
||||
- 浏览本索引找到相关章节
|
||||
- 或用 Ctrl+F 搜索关键词
|
||||
|
||||
### 第二步:查看文档中的相关部分
|
||||
- DEPLOYMENT.md 的相关章节
|
||||
- 或 VERIFICATION.md 的故障排查部分
|
||||
|
||||
### 第三步:运行诊断命令
|
||||
- QUICK_REFERENCE.md 的 "故障排查" 部分
|
||||
- 或 VERIFICATION.md 的 "故障排查" 部分
|
||||
|
||||
### 第四步:检查日志
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
### 第五步:查看详细文档
|
||||
如果上述步骤未能解决,查看对应的详细文档:
|
||||
- 配置问题 → DEPLOYMENT.md
|
||||
- 权限问题 → VERIFICATION.md 第2/11部分
|
||||
- 集成问题 → INTEGRATION.md
|
||||
- 加密问题 → ENCRYPTION.md
|
||||
|
||||
---
|
||||
|
||||
## 📞 文档反馈
|
||||
|
||||
如果您发现:
|
||||
- ❌ 文档不清楚
|
||||
- ❌ 命令不工作
|
||||
- ❌ 信息缺失或过时
|
||||
- ❌ 错别字或格式问题
|
||||
|
||||
请在相应的 `.md` 文件中标记,或提交更新建议。
|
||||
|
||||
---
|
||||
|
||||
## 📌 关键概念快速链接
|
||||
|
||||
| 概念 | 详见 |
|
||||
|-----|------|
|
||||
| JWT 令牌生命周期 | SUMMARY.md "关键特性" |
|
||||
| Redis 双键结构 | SUMMARY.md "关键特性" |
|
||||
| RBAC 权限隔离 | SUMMARY.md "关键特性" |
|
||||
| CSRF 防护 | SUMMARY.md "关键特性" |
|
||||
| ETCD 加密 | ENCRYPTION.md |
|
||||
| 错误处理 | INTEGRATION.md 第8部分 |
|
||||
| 密钥轮换 | DEPLOYMENT.md "安全最佳实践" |
|
||||
| 灾难恢复 | DEPLOYMENT.md "灾难恢复" |
|
||||
|
||||
---
|
||||
|
||||
## ✨ 文档特性
|
||||
|
||||
✅ **模块化** - 每个文档独立,但相互链接
|
||||
✅ **分层** - 从快速概览到深度细节
|
||||
✅ **实践导向** - 包含实际命令和代码示例
|
||||
✅ **完整性** - 覆盖部署、验证、维护、故障排查
|
||||
✅ **易查找** - 目录、索引、速查表
|
||||
|
||||
---
|
||||
|
||||
**开始阅读:** 👉 [SUMMARY.md](./SUMMARY.md)
|
||||
|
||||
或根据您的角色选择:
|
||||
|
||||
| 角色 | 开始文档 | 预计时间 |
|
||||
|-----|--------|--------|
|
||||
| DevOps/运维 | [DEPLOYMENT.md](./DEPLOYMENT.md) | 1-2 小时 |
|
||||
| 应用开发 | [INTEGRATION.md](../api/INTEGRATION.md) | 2-3 小时 |
|
||||
| 安全审查 | [ENCRYPTION.md](./ENCRYPTION.md) | 30 分钟 |
|
||||
| 项目经理 | [SUMMARY.md](./SUMMARY.md) | 15 分钟 |
|
||||
| 新手 | [README.md](./README.md) → [SUMMARY.md](./SUMMARY.md) | 15-20 分钟 |
|
||||
# JWT + ETCD 加密系统文档索引
|
||||
|
||||
## 📚 文档完整导航
|
||||
|
||||
### 快速入门 (5-15分钟)
|
||||
|
||||
**推荐路径:** 从上到下顺序阅读
|
||||
|
||||
1. **[SUMMARY.md](./SUMMARY.md)** ⭐ 从这里开始
|
||||
- 📋 项目概览和架构图
|
||||
- 🎯 核心特性一览
|
||||
- ✅ 生产就绪检查清单
|
||||
- 🚀 下一步行动计划
|
||||
|
||||
2. **[README.md](./README.md)**
|
||||
- 🏃 4个快速部署步骤
|
||||
- 🔐 安全考虑事项
|
||||
- 🔄 密钥轮换程序
|
||||
- 🆘 故障排查
|
||||
|
||||
3. **[QUICK_REFERENCE.md](./QUICK_REFERENCE.md)**
|
||||
- ⚡ 一页速查表
|
||||
- 📝 常见命令复制粘贴
|
||||
- 🗺️ 文档地图
|
||||
- 🎓 关键参数速知
|
||||
|
||||
### 部署实施 (30-60分钟)
|
||||
|
||||
4. **[DEPLOYMENT.md](./DEPLOYMENT.md)** - 最详细的部署指南
|
||||
- 📦 第1步:创建 Secret 和 RBAC(必需)
|
||||
- 🔄 第2步:更新 user-rpc Deployment
|
||||
- 🌐 第3步:更新 Envoy Gateway Deployment
|
||||
- 🔐 第4步:启用 ETCD 加密(生产推荐)
|
||||
- ✔️ 第5步:验证整个系统
|
||||
- 📊 监控和日志配置
|
||||
- 🛠️ 安全最佳实践
|
||||
- 🆘 故障排查指南
|
||||
- 💾 灾难恢复流程
|
||||
|
||||
### 验证和监控 (20-30分钟)
|
||||
|
||||
5. **[VERIFICATION.md](./VERIFICATION.md)** - 完整验证清单
|
||||
|
||||
**12个验证部分:**
|
||||
|
||||
| 部分 | 用途 | 时间 |
|
||||
|-----|------|------|
|
||||
| 第1部分 | Secret/RBAC 基础验证 | 2分钟 |
|
||||
| 第2部分 | 权限测试(allow/deny) | 3分钟 |
|
||||
| 第3部分 | Deployment 配置检查 | 2分钟 |
|
||||
| 第4部分 | Redis 连接测试 | 2分钟 |
|
||||
| 第5部分 | 应用启动日志 | 3分钟 |
|
||||
| 第6部分 | 网络和服务发现 | 3分钟 |
|
||||
| 第7部分 | Prometheus 指标 | 3分钟 |
|
||||
| 第8部分 | Loki 日志聚合 | 2分钟 |
|
||||
| 第9部分 | ETCD 加密验证 | 5分钟 |
|
||||
| 第10部分 | JWT 功能测试 | 10分钟 |
|
||||
| 第11部分 | 故障排查诊断 | 5分钟 |
|
||||
| 第12部分 | 清理和总结 | 2分钟 |
|
||||
|
||||
### 高级话题
|
||||
|
||||
6. **[ENCRYPTION.md](./ENCRYPTION.md)** - ETCD 加密完整指南
|
||||
- 🔑 第1部分:密钥生成
|
||||
- 📋 第2部分:配置格式
|
||||
- 🔧 第3部分:kube-apiserver 修改
|
||||
- ✅第4部分:验证加密
|
||||
- ⚠️ 第5部分:关键警告(数据不可恢复)
|
||||
- 🔐 第6部分:RBAC 解释
|
||||
- 📦 第7部分:Deployment 示例
|
||||
- 🍎 第8部分:Minikube 特定说明
|
||||
|
||||
7. **[INTEGRATION.md](../api/INTEGRATION.md)** - 代码集成指南
|
||||
- 🔗 第1部分:gRPC Unary Interceptor
|
||||
- 🔗 第2部分:gRPC Stream Interceptor
|
||||
- 👤 第3部分:登录 Handler 实现
|
||||
- 🔐 第4部分:受保护 Handler 中的声明提取
|
||||
- 🔄 第5部分:令牌刷新端点
|
||||
- 🚪 第6部分:登出处理
|
||||
- 🛣️ 第7部分:REST Routes 配置
|
||||
- 🔎 第8部分:错误处理最佳实践
|
||||
- 🧪 第9部分:单元测试示例
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件结构详解
|
||||
|
||||
```
|
||||
deploy/k8s/
|
||||
├── secrets/
|
||||
│ ├── jwt-secret.yaml ✅ Kubernetes 清单文件
|
||||
│ │ ├── Secret: jwt-secret (JWT 秘钥数据)
|
||||
│ │ ├── ServiceAccount: user-rpc
|
||||
│ │ ├── ServiceAccount: envoy-gateway
|
||||
│ │ ├── Role: jwt-secret-reader
|
||||
│ │ ├── RoleBinding: jwt-secret-reader-user-rpc
|
||||
│ │ └── RoleBinding: jwt-secret-reader-envoy-gateway
|
||||
│ │
|
||||
│ ├── README.md 📖 快速参考指南(5分钟入门)
|
||||
│ ├── SUMMARY.md 📊 系统概览(10分钟了解全貌)
|
||||
│ ├── QUICK_REFERENCE.md ⚡ 速查表(查找命令和参数)
|
||||
│ ├── DEPLOYMENT.md 📦 详细部署指南(60分钟完整部署)
|
||||
│ ├── ENCRYPTION.md 🔐 ETCD 加密指南(Control Plane 配置)
|
||||
│ ├── VERIFICATION.md ✅ 验证清单(部署后验证)
|
||||
│ └── INDEX.md 🗺️ 本文件(文档导航)
|
||||
│
|
||||
└── envoy/
|
||||
│ └── envoy.yaml ✅ Envoy 网关配置
|
||||
│ └── 已更新: serviceAccountName: envoy-gateway
|
||||
│
|
||||
service/user/
|
||||
├── user-api.yaml ✅ user-api Service
|
||||
├── user-rpc.yaml ✅ user-rpc Deployment(已更新)
|
||||
│ ├── serviceAccountName: user-rpc (已更新)
|
||||
│ ├── JWT_SECRET_KEY env var (已更新)
|
||||
│ └── Redis Cluster configuration
|
||||
└── ...
|
||||
|
||||
app/users/
|
||||
├── api/
|
||||
│ └── INTEGRATION.md 📝 REST/gRPC 集成指南
|
||||
│
|
||||
└── rpc/
|
||||
├── internal/utils/jwt.go ✅ JwtManager 实现(已存在)
|
||||
├── internal/config/config.go ✅ JWT 配置(已存在)
|
||||
├── internal/svc/
|
||||
│ └── serviceContext.go ✅ 依赖注入(已存在)
|
||||
└── etc/pb.yaml ✅ 运行时配置(已存在)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 按场景查找文档
|
||||
|
||||
### 场景 1:我想快速了解这个系统是什么
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [SUMMARY.md](./SUMMARY.md) - 项目概览(5分钟)
|
||||
2. [SUMMARY.md](./SUMMARY.md) 中的架构图和特性说明
|
||||
|
||||
**关键信息:**
|
||||
- JWT 令牌系统 + Redis 存储 + RBAC 权限 + ETCD 加密
|
||||
- 支持 7 天有效期、30 天可刷新
|
||||
- Envoy 网关 CSRF 防护
|
||||
|
||||
---
|
||||
|
||||
### 场景 2:我想立即部署到 Kubernetes
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [README.md](./README.md) - 快速参考(2分钟)
|
||||
2. [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - 复制粘贴命令(3分钟)
|
||||
3. 运行部署命令(5分钟)
|
||||
4. [VERIFICATION.md](./VERIFICATION.md) 第1-7部分 - 验证(10分钟)
|
||||
|
||||
**快速命令:**
|
||||
```bash
|
||||
# Copy from QUICK_REFERENCE.md "部署命令" 部分
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 场景 3:部署后验证一切正常
|
||||
|
||||
**推荐阅读:**
|
||||
- [VERIFICATION.md](./VERIFICATION.md) - 12部分完整验证清单
|
||||
- 逐部分执行验证命令
|
||||
|
||||
**预计时间:** 30-40分钟
|
||||
|
||||
**验证触发点:**
|
||||
- ✅ Secrets 和 RBAC 已创建
|
||||
- ✅ Pods 已启动运行
|
||||
- ✅ 权限验证通过
|
||||
- ✅ Redis 连接成功
|
||||
|
||||
---
|
||||
|
||||
### 场景 4:启用 ETCD 加密(生产推荐)
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [ENCRYPTION.md](./ENCRYPTION.md) - 完整加密指南
|
||||
2. 按照 8 个步骤逐一执行
|
||||
3. [VERIFICATION.md](./VERIFICATION.md) 第9部分 - 加密验证
|
||||
|
||||
**需要的权限:**
|
||||
- Control Plane 节点的 root/sudo 权限
|
||||
- Kubernetes 集群管理员权限
|
||||
|
||||
**预计时间:** 15-20分钟
|
||||
|
||||
---
|
||||
|
||||
### 场景 5:集成 JWT 到我的应用代码中
|
||||
|
||||
**推荐阅读顺序:**
|
||||
1. [INTEGRATION.md](../api/INTEGRATION.md) 第1-2部分 - gRPC 拦截器
|
||||
2. 第3-4部分 - 登录和受保护 Handlers
|
||||
3. 第7-8部分 - REST API 中间件
|
||||
4. 第9部分 - 单元测试
|
||||
|
||||
**需要实现:**
|
||||
- ✅ gRPC Unary/Stream Interceptors
|
||||
- ✅ 登录/登出端点
|
||||
- ✅ JWT Middleware for REST
|
||||
- ✅ 错误处理
|
||||
|
||||
**预计时间:** 2-3 小时
|
||||
|
||||
---
|
||||
|
||||
### 场景 6:部署后遇到问题
|
||||
|
||||
**根据错误类型选择:**
|
||||
|
||||
| 错误类型 | 查看文档 |
|
||||
|---------|--------|
|
||||
| Pod 无法启动 | [VERIFICATION.md](./VERIFICATION.md) 第11部分 |
|
||||
| 权限被拒绝 | [VERIFICATION.md](./VERIFICATION.md) 第2部分 + [README.md](./README.md) 故障排查 |
|
||||
| Redis 连接失败 | [VERIFICATION.md](./VERIFICATION.md) 第4部分 |
|
||||
| ETCD 加密失败 | [ENCRYPTION.md](./ENCRYPTION.md) 第5-6部分 |
|
||||
| 配置不清楚 | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 配置文件位置 |
|
||||
|
||||
---
|
||||
|
||||
### 场景 7:定期维护任务
|
||||
|
||||
#### 任务:轮换 JWT 秘钥
|
||||
|
||||
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 安全最佳实践 > 密钥轮换
|
||||
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 密钥轮换步骤
|
||||
|
||||
**频率:** 季度(3个月)
|
||||
|
||||
#### 任务:轮换 ETCD 加密密钥
|
||||
|
||||
**阅读:** [ENCRYPTION.md](./ENCRYPTION.md) 第5部分
|
||||
**或:** [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) ETCD 加密系统
|
||||
|
||||
**频率:** 年度(12个月)
|
||||
|
||||
#### 任务:备份密钥
|
||||
|
||||
**阅读:** [DEPLOYMENT.md](./DEPLOYMENT.md) 灾难恢复
|
||||
**或:** [ENCRYPTION.md](./ENCRYPTION.md) 关键警告
|
||||
|
||||
**频率:** 立即 + 每次轮换后
|
||||
|
||||
---
|
||||
|
||||
## 📊 文档深度对比
|
||||
|
||||
| 文档 | 深度 | 丰富度 | 代码 | 适合角色 |
|
||||
|-----|------|--------|------|---------|
|
||||
| README | 浅 | 概览 | - | PM/初学者 |
|
||||
| SUMMARY | 浅 | 概览 | - | 决策者 |
|
||||
| QUICK_REFERENCE | 中 | 速查 | 命令 | DevOps/SRE |
|
||||
| DEPLOYMENT | 深 | 详细 | 示例 | DevOps/运维 |
|
||||
| VERIFICATION | 深 | 详细 | 脚本 | QA/DevOps |
|
||||
| ENCRYPTION | 非常深 | 极详细 | YAML | 安全/运维 |
|
||||
| INTEGRATION | 非常深 | 代码级 | 完整 | 开发者 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 学习路径建议
|
||||
|
||||
### 对于 DevOps/SRE
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. DEPLOYMENT.md (30 分钟)
|
||||
3. VERIFICATION.md (30 分钟)
|
||||
4. ENCRYPTION.md (20 分钟)
|
||||
5. 实践部署 (60 分钟)
|
||||
|
||||
**总计:** ~3 小时
|
||||
|
||||
### 对于应用开发者
|
||||
|
||||
1. SUMMARY.md > "集成点" 部分 (5 分钟)
|
||||
2. INTEGRATION.md (60 分钟)
|
||||
3. QUICK_REFERENCE.md > "JWT Manager API" (10 分钟)
|
||||
4. 代码实现 (2-3 小时)
|
||||
5. 单元测试 (INTEGRATION.md 第9部分)
|
||||
|
||||
**总计:** ~4 小时
|
||||
|
||||
### 对于安全/合规人员
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. ENCRYPTION.md (30 分钟)
|
||||
3. DEPLOYMENT.md > 安全最佳实践 (15 分钟)
|
||||
4. VERIFICATION.md 第9部分 (10 分钟)
|
||||
|
||||
**总计:** ~1 小时
|
||||
|
||||
### 对于项目经理
|
||||
|
||||
1. SUMMARY.md (5 分钟)
|
||||
2. DEPLOYMENT.md > "部署状态示意图" (5 分钟)
|
||||
3. DEPLOYMENT.md > "快速部署" (2 分钟)
|
||||
|
||||
**总计:** ~15 分钟
|
||||
|
||||
---
|
||||
|
||||
## 🎓 学习成果预期
|
||||
|
||||
### 完成后,您将能够:
|
||||
|
||||
✅ 在 Kubernetes 中部署 JWT 认证系统
|
||||
✅ 配置 RBAC 权限控制
|
||||
✅ 启用 ETCD 加密保护敏感数据
|
||||
✅ 在 Go-zero 应用中集成 JWT
|
||||
✅ 实现令牌刷新和撤销
|
||||
✅ 诊断和排查常见问题
|
||||
✅ 执行密钥轮换和灾难恢复
|
||||
|
||||
---
|
||||
|
||||
## 🆘 求助指南
|
||||
|
||||
### 第一步:找到相关文档
|
||||
- 浏览本索引找到相关章节
|
||||
- 或用 Ctrl+F 搜索关键词
|
||||
|
||||
### 第二步:查看文档中的相关部分
|
||||
- DEPLOYMENT.md 的相关章节
|
||||
- 或 VERIFICATION.md 的故障排查部分
|
||||
|
||||
### 第三步:运行诊断命令
|
||||
- QUICK_REFERENCE.md 的 "故障排查" 部分
|
||||
- 或 VERIFICATION.md 的 "故障排查" 部分
|
||||
|
||||
### 第四步:检查日志
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
### 第五步:查看详细文档
|
||||
如果上述步骤未能解决,查看对应的详细文档:
|
||||
- 配置问题 → DEPLOYMENT.md
|
||||
- 权限问题 → VERIFICATION.md 第2/11部分
|
||||
- 集成问题 → INTEGRATION.md
|
||||
- 加密问题 → ENCRYPTION.md
|
||||
|
||||
---
|
||||
|
||||
## 📞 文档反馈
|
||||
|
||||
如果您发现:
|
||||
- ❌ 文档不清楚
|
||||
- ❌ 命令不工作
|
||||
- ❌ 信息缺失或过时
|
||||
- ❌ 错别字或格式问题
|
||||
|
||||
请在相应的 `.md` 文件中标记,或提交更新建议。
|
||||
|
||||
---
|
||||
|
||||
## 📌 关键概念快速链接
|
||||
|
||||
| 概念 | 详见 |
|
||||
|-----|------|
|
||||
| JWT 令牌生命周期 | SUMMARY.md "关键特性" |
|
||||
| Redis 双键结构 | SUMMARY.md "关键特性" |
|
||||
| RBAC 权限隔离 | SUMMARY.md "关键特性" |
|
||||
| CSRF 防护 | SUMMARY.md "关键特性" |
|
||||
| ETCD 加密 | ENCRYPTION.md |
|
||||
| 错误处理 | INTEGRATION.md 第8部分 |
|
||||
| 密钥轮换 | DEPLOYMENT.md "安全最佳实践" |
|
||||
| 灾难恢复 | DEPLOYMENT.md "灾难恢复" |
|
||||
|
||||
---
|
||||
|
||||
## ✨ 文档特性
|
||||
|
||||
✅ **模块化** - 每个文档独立,但相互链接
|
||||
✅ **分层** - 从快速概览到深度细节
|
||||
✅ **实践导向** - 包含实际命令和代码示例
|
||||
✅ **完整性** - 覆盖部署、验证、维护、故障排查
|
||||
✅ **易查找** - 目录、索引、速查表
|
||||
|
||||
---
|
||||
|
||||
**开始阅读:** 👉 [SUMMARY.md](./SUMMARY.md)
|
||||
|
||||
或根据您的角色选择:
|
||||
|
||||
| 角色 | 开始文档 | 预计时间 |
|
||||
|-----|--------|--------|
|
||||
| DevOps/运维 | [DEPLOYMENT.md](./DEPLOYMENT.md) | 1-2 小时 |
|
||||
| 应用开发 | [INTEGRATION.md](../api/INTEGRATION.md) | 2-3 小时 |
|
||||
| 安全审查 | [ENCRYPTION.md](./ENCRYPTION.md) | 30 分钟 |
|
||||
| 项目经理 | [SUMMARY.md](./SUMMARY.md) | 15 分钟 |
|
||||
| 新手 | [README.md](./README.md) → [SUMMARY.md](./SUMMARY.md) | 15-20 分钟 |
|
||||
|
||||
+350
-350
@@ -1,350 +1,350 @@
|
||||
# JWT + ETCD 加密系统 - 快速参考卡片
|
||||
|
||||
## 一页速查表
|
||||
|
||||
### 部署命令
|
||||
|
||||
```bash
|
||||
# 创建 Secret 和 RBAC
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
|
||||
# 更新 Deployments
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 验证部署
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
kubectl get sa user-rpc envoy-gateway -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=envoy-gateway
|
||||
```
|
||||
|
||||
### 权限验证
|
||||
|
||||
```bash
|
||||
# user-rpc 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets \
|
||||
--as=system:serviceaccount:juwan:user-rpc \
|
||||
--resource-name=jwt-secret -n juwan
|
||||
# 预期: yes
|
||||
|
||||
# 其他 SA 无法读 jwt-secret
|
||||
kubectl auth can-i get secrets \
|
||||
--as=system:serviceaccount:juwan:default \
|
||||
--resource-name=jwt-secret -n juwan
|
||||
# 预期: no
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# user-rpc 日志
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
|
||||
# Envoy 日志
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
|
||||
# 特定 Pod 日志
|
||||
kubectl logs -n juwan <pod-name> --all-containers=true -f
|
||||
```
|
||||
|
||||
### 环境变量验证
|
||||
|
||||
```bash
|
||||
# 检查 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
|
||||
```
|
||||
|
||||
### Redis 验证
|
||||
|
||||
```bash
|
||||
# 连接到 Redis Cluster
|
||||
kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \
|
||||
redis-cli -h user-redis.juwan:6379 -c CLUSTER INFO
|
||||
|
||||
# 测试键操作
|
||||
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
|
||||
```
|
||||
|
||||
### ETCD 加密配置
|
||||
|
||||
```bash
|
||||
# 1. 在 Control Plane 节点生成密钥
|
||||
head -c 32 /dev/urandom | base64
|
||||
|
||||
# 2. 编辑 kube-apiserver 清单
|
||||
sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
|
||||
# 添加参数:
|
||||
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
|
||||
# 3. 创建加密配置文件
|
||||
cat <<EOF | sudo tee /etc/kubernetes/encryption-config.yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: <BASE64_KEY>
|
||||
- identity: {}
|
||||
EOF
|
||||
|
||||
# 4. 验证加密
|
||||
kubectl create secret generic test-encryption -n juwan --from-literal=key=value
|
||||
sudo ETCDCTL_API=3 etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt \
|
||||
--key=/etc/kubernetes/pki/etcd/server.key \
|
||||
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
|
||||
--endpoints=127.0.0.1:2379 \
|
||||
get /registry/secrets/juwan/test-encryption | od -A x -t x1z
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
```bash
|
||||
# Pod 无法启动?查看事件
|
||||
kubectl describe pod <pod-name> -n juwan
|
||||
|
||||
# 权限被拒绝?检查 RBAC
|
||||
kubectl get rolebinding -n juwan -o wide
|
||||
kubectl describe rolebinding jwt-secret-reader-user-rpc -n juwan
|
||||
|
||||
# 无法挂载 Secret?检查 Secret 存在性
|
||||
kubectl get secret jwt-secret -n juwan -o yaml
|
||||
|
||||
# Redis 连接错误?测试连通性
|
||||
kubectl exec -it <user-rpc-pod> -n juwan -- \
|
||||
redis-cli -h user-redis.juwan:6379 PING
|
||||
```
|
||||
|
||||
## JWT Manager API 速查
|
||||
|
||||
### JwtManager 方法
|
||||
|
||||
```go
|
||||
// 生成新令牌
|
||||
token, err := svcCtx.JwtManager.New(ctx, userID, email, name)
|
||||
|
||||
// 验证令牌
|
||||
claims, err := svcCtx.JwtManager.Valid(ctx, token)
|
||||
|
||||
// 刷新令牌(如果过期但 Redis 仍有数据)
|
||||
newToken, err := svcCtx.JwtManager.Renew(ctx, token)
|
||||
|
||||
// 提取声明(不验证签名)
|
||||
claims, err := svcCtx.JwtManager.Extract(ctx, token)
|
||||
|
||||
// 检查令牌是否存在于 Redis
|
||||
exists, err := svcCtx.JwtManager.Exists(ctx, token)
|
||||
|
||||
// 撤销令牌(登出)
|
||||
err := svcCtx.JwtManager.Revoke(ctx, userID, token)
|
||||
|
||||
// 获取用户当前令牌
|
||||
token, err := svcCtx.JwtManager.GetUserToken(ctx, userID)
|
||||
|
||||
// 将声明转换为载荷
|
||||
payload := svcCtx.JwtManager.ClaimsToPayload(claims)
|
||||
```
|
||||
|
||||
## 配置文件位置
|
||||
|
||||
| 配置 | 位置 | 关键参数 |
|
||||
|-----|------|--------|
|
||||
| JWT Secret | `deploy/k8s/secrets/jwt-secret.yaml` | `secret-key` |
|
||||
| user-rpc 配置 | `app/users/rpc/etc/pb.yaml` | `JWT.SecretKey`, `REDIS_HOST` |
|
||||
| Envoy 配置 | `deploy/k8s/envoy/envoy.yaml` | CSRF 验证 Lua 代码 |
|
||||
| ETCD 加密 | `/etc/kubernetes/encryption-config.yaml`(Control Plane) | `secret` (32字节密钥) |
|
||||
|
||||
## 关键参数速查
|
||||
|
||||
```yaml
|
||||
# JWT 令牌有效期
|
||||
Token Exp: 7 days
|
||||
|
||||
# Redis 存储 TTL
|
||||
Redis TTL: 30 days
|
||||
|
||||
# 可刷新时间窗口
|
||||
Refresh Window: 30 days - 7 days = 23 days
|
||||
|
||||
# CSRF Token 位置
|
||||
Cookie: csrf_token=...
|
||||
Header: X-CSRF-Token: ...
|
||||
|
||||
# ETCD 加密算法
|
||||
Algorithm: AES-CBC
|
||||
Key Size: 256 bits (32 bytes)
|
||||
Encoding: Base64
|
||||
|
||||
# Secret 挂载方式
|
||||
Method: volumeMount (read-only)
|
||||
或
|
||||
Method: valueFrom.secretKeyRef
|
||||
```
|
||||
|
||||
## 常见问题速查
|
||||
|
||||
| 问题 | 排查命令 | 解决方案 |
|
||||
|-----|--------|--------|
|
||||
| Pod 无法启动 | `kubectl describe pod` | 检查 Secret/RBAC |
|
||||
| 权限被拒绝 | `kubectl auth can-i get secrets` | 验证 RBAC 绑定 |
|
||||
| Redis 连接失败 | `redis-cli PING` | 检查 Redis Pods |
|
||||
| JWT 验证失败 | 查看 Pod 日志 | 检查 Redis 中的令牌 |
|
||||
| CSRF 验证失败 | 查看 Envoy 日志 | 检查 Cookie/Header 匹配 |
|
||||
| ETCD 加密失败 | `kubectl get secret` | 检查 kube-apiserver 启动参数 |
|
||||
|
||||
## 部署检查清单 (5分钟版)
|
||||
|
||||
```bash
|
||||
# 第1步: 部署 Secret (10秒)
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml && sleep 5
|
||||
|
||||
# 第2步: 验证 Secret (10秒)
|
||||
kubectl get secret jwt-secret -n juwan && echo "✓ Secret 已创建"
|
||||
|
||||
# 第3步: 验证 RBAC (10秒)
|
||||
kubectl get role jwt-secret-reader -n juwan && echo "✓ RBAC 已配置"
|
||||
|
||||
# 第4步: 更新 Deployments (20秒)
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 第5步: 等待 Pods 启动 (30秒)
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 第6步: 快速功能测试 (2分钟)
|
||||
# 创建一个令牌并验证可读取
|
||||
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:*"
|
||||
```
|
||||
|
||||
## 密钥轮换步骤
|
||||
|
||||
```bash
|
||||
# 1. 生成新密钥
|
||||
NEW_KEY=$(head -c 32 /dev/urandom | base64)
|
||||
|
||||
# 2. 更新 Secret
|
||||
kubectl create secret generic jwt-secret \
|
||||
--from-literal=secret-key=$NEW_KEY \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 3. 重启 Pods(自动挂载新 Secret)
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
|
||||
# 4. 等待 Pods 启动
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 5. 旧令牌现在需要刷新或重新登录
|
||||
```
|
||||
|
||||
## 文档地图
|
||||
|
||||
```
|
||||
deploy/k8s/secrets/
|
||||
├── jwt-secret.yaml ← Secrets + RBAC 配置
|
||||
├── README.md ← 开始阅读(快速指南)
|
||||
├── SUMMARY.md ← 本文件(系统概览)
|
||||
├── DEPLOYMENT.md ← 详细部署步骤(12步)
|
||||
├── ENCRYPTION.md ← ETCD 加密详细指南
|
||||
├── VERIFICATION.md ← 完整验证清单(12部分)
|
||||
└── QUICK_REFERENCE.md ← 本快速参考卡片
|
||||
|
||||
app/users/api/
|
||||
└── INTEGRATION.md ← JWT 代码集成指南
|
||||
|
||||
app/users/rpc/
|
||||
├── internal/utils/jwt.go ← JwtManager 实现
|
||||
├── internal/config/config.go ← JWT 配置
|
||||
├── internal/svc/serviceContext.go ← 依赖注入
|
||||
└── etc/pb.yaml ← 运行时配置
|
||||
```
|
||||
|
||||
## 关键时间点
|
||||
|
||||
| 阶段 | 时间 | 操作 |
|
||||
|-----|------|------|
|
||||
| 令牌签发 | T0 | 生成 JWT,过期时间 = T0 + 7天 |
|
||||
| | | 在 Redis 存储,TTL = 30天 |
|
||||
| Token 过期 | T0 + 7天 | JWT 验证失败 |
|
||||
| 令牌刷新 | T0 + 7天到T0 + 30天 | 如果 Redis 仍有数据,生成新令牌 |
|
||||
| 完全失效 | T0 + 30天 | Redis 删除,无法再刷新 |
|
||||
| 重新登录 | T0 + 30天+ | 用户需要重新登录 |
|
||||
|
||||
## 性能提示
|
||||
|
||||
```bash
|
||||
# 高并发下优化 Redis 连接
|
||||
# 在 pb.yaml 中调整:
|
||||
CacheConf:
|
||||
- Host: "user-redis.juwan:6379"
|
||||
Type: "cluster"
|
||||
MaxConnections: 100
|
||||
ConnectionPoolSize: 50
|
||||
|
||||
# 监控 JWT 验证吞吐量
|
||||
# 在 Prometheus 查询:
|
||||
rate(jwt_validations_total[5m])
|
||||
rate(jwt_refresh_total[5m])
|
||||
```
|
||||
|
||||
## 安全提示
|
||||
|
||||
✅ **必做**
|
||||
- [ ] 定期轮换 JWT 秘钥(季度)
|
||||
- [ ] 定期轮换 ETCD 加密密钥(年度)
|
||||
- [ ] 备份加密密钥到安全位置
|
||||
- [ ] 启用审计日志
|
||||
- [ ] 监控异常的令牌验证失败
|
||||
|
||||
❌ **禁止**
|
||||
- [ ] 不要在日志中输出 JWT 秘钥
|
||||
- [ ] 不要在代码库中存储密钥
|
||||
- [ ] 不要发送明文密钥到 Slack/Email
|
||||
- [ ] 不要在多个环境间共享密钥
|
||||
|
||||
## 版本信息
|
||||
|
||||
```
|
||||
Kubernetes: 1.24+
|
||||
Go-zero: v1.10.0+
|
||||
Redis: 7.0+
|
||||
ETCD: 3.5+
|
||||
Envoy: v1.32.2+
|
||||
```
|
||||
|
||||
## 支持和反馈
|
||||
|
||||
遇到问题?按优先级检查:
|
||||
|
||||
1. **运行验证脚本**
|
||||
```bash
|
||||
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
./deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
2. **查看日志**
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
```
|
||||
|
||||
3. **阅读 VERIFICATION.md**
|
||||
- 第1-5部分: 基础配置
|
||||
- 第6-8部分: 网络和监控
|
||||
- 第9部分: ETCD 加密
|
||||
- 第11部分: 故障排查
|
||||
|
||||
4. **详细指南**
|
||||
- DEPLOYMENT.md - 完整步骤
|
||||
- INTEGRATION.md - 代码集成
|
||||
- ENCRYPTION.md - 加密配置
|
||||
# JWT + ETCD 加密系统 - 快速参考卡片
|
||||
|
||||
## 一页速查表
|
||||
|
||||
### 部署命令
|
||||
|
||||
```bash
|
||||
# 创建 Secret 和 RBAC
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
|
||||
# 更新 Deployments
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 验证部署
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
kubectl get sa user-rpc envoy-gateway -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=envoy-gateway
|
||||
```
|
||||
|
||||
### 权限验证
|
||||
|
||||
```bash
|
||||
# user-rpc 可以读 jwt-secret
|
||||
kubectl auth can-i get secrets \
|
||||
--as=system:serviceaccount:juwan:user-rpc \
|
||||
--resource-name=jwt-secret -n juwan
|
||||
# 预期: yes
|
||||
|
||||
# 其他 SA 无法读 jwt-secret
|
||||
kubectl auth can-i get secrets \
|
||||
--as=system:serviceaccount:juwan:default \
|
||||
--resource-name=jwt-secret -n juwan
|
||||
# 预期: no
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# user-rpc 日志
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
|
||||
# Envoy 日志
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
|
||||
# 特定 Pod 日志
|
||||
kubectl logs -n juwan <pod-name> --all-containers=true -f
|
||||
```
|
||||
|
||||
### 环境变量验证
|
||||
|
||||
```bash
|
||||
# 检查 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
|
||||
```
|
||||
|
||||
### Redis 验证
|
||||
|
||||
```bash
|
||||
# 连接到 Redis Cluster
|
||||
kubectl run redis-cli --image=redis:latest --rm -it --restart=Never -- \
|
||||
redis-cli -h user-redis.juwan:6379 -c CLUSTER INFO
|
||||
|
||||
# 测试键操作
|
||||
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
|
||||
```
|
||||
|
||||
### ETCD 加密配置
|
||||
|
||||
```bash
|
||||
# 1. 在 Control Plane 节点生成密钥
|
||||
head -c 32 /dev/urandom | base64
|
||||
|
||||
# 2. 编辑 kube-apiserver 清单
|
||||
sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
|
||||
# 添加参数:
|
||||
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml
|
||||
|
||||
# 3. 创建加密配置文件
|
||||
cat <<EOF | sudo tee /etc/kubernetes/encryption-config.yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: <BASE64_KEY>
|
||||
- identity: {}
|
||||
EOF
|
||||
|
||||
# 4. 验证加密
|
||||
kubectl create secret generic test-encryption -n juwan --from-literal=key=value
|
||||
sudo ETCDCTL_API=3 etcdctl --cert=/etc/kubernetes/pki/etcd/server.crt \
|
||||
--key=/etc/kubernetes/pki/etcd/server.key \
|
||||
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
|
||||
--endpoints=127.0.0.1:2379 \
|
||||
get /registry/secrets/juwan/test-encryption | od -A x -t x1z
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
```bash
|
||||
# Pod 无法启动?查看事件
|
||||
kubectl describe pod <pod-name> -n juwan
|
||||
|
||||
# 权限被拒绝?检查 RBAC
|
||||
kubectl get rolebinding -n juwan -o wide
|
||||
kubectl describe rolebinding jwt-secret-reader-user-rpc -n juwan
|
||||
|
||||
# 无法挂载 Secret?检查 Secret 存在性
|
||||
kubectl get secret jwt-secret -n juwan -o yaml
|
||||
|
||||
# Redis 连接错误?测试连通性
|
||||
kubectl exec -it <user-rpc-pod> -n juwan -- \
|
||||
redis-cli -h user-redis.juwan:6379 PING
|
||||
```
|
||||
|
||||
## JWT Manager API 速查
|
||||
|
||||
### JwtManager 方法
|
||||
|
||||
```go
|
||||
// 生成新令牌
|
||||
token, err := svcCtx.JwtManager.New(ctx, userID, email, name)
|
||||
|
||||
// 验证令牌
|
||||
claims, err := svcCtx.JwtManager.Valid(ctx, token)
|
||||
|
||||
// 刷新令牌(如果过期但 Redis 仍有数据)
|
||||
newToken, err := svcCtx.JwtManager.Renew(ctx, token)
|
||||
|
||||
// 提取声明(不验证签名)
|
||||
claims, err := svcCtx.JwtManager.Extract(ctx, token)
|
||||
|
||||
// 检查令牌是否存在于 Redis
|
||||
exists, err := svcCtx.JwtManager.Exists(ctx, token)
|
||||
|
||||
// 撤销令牌(登出)
|
||||
err := svcCtx.JwtManager.Revoke(ctx, userID, token)
|
||||
|
||||
// 获取用户当前令牌
|
||||
token, err := svcCtx.JwtManager.GetUserToken(ctx, userID)
|
||||
|
||||
// 将声明转换为载荷
|
||||
payload := svcCtx.JwtManager.ClaimsToPayload(claims)
|
||||
```
|
||||
|
||||
## 配置文件位置
|
||||
|
||||
| 配置 | 位置 | 关键参数 |
|
||||
|-----|------|--------|
|
||||
| JWT Secret | `deploy/k8s/secrets/jwt-secret.yaml` | `secret-key` |
|
||||
| user-rpc 配置 | `app/users/rpc/etc/pb.yaml` | `JWT.SecretKey`, `REDIS_HOST` |
|
||||
| Envoy 配置 | `deploy/k8s/envoy/envoy.yaml` | CSRF 验证 Lua 代码 |
|
||||
| ETCD 加密 | `/etc/kubernetes/encryption-config.yaml`(Control Plane) | `secret` (32字节密钥) |
|
||||
|
||||
## 关键参数速查
|
||||
|
||||
```yaml
|
||||
# JWT 令牌有效期
|
||||
Token Exp: 7 days
|
||||
|
||||
# Redis 存储 TTL
|
||||
Redis TTL: 30 days
|
||||
|
||||
# 可刷新时间窗口
|
||||
Refresh Window: 30 days - 7 days = 23 days
|
||||
|
||||
# CSRF Token 位置
|
||||
Cookie: csrf_token=...
|
||||
Header: X-CSRF-Token: ...
|
||||
|
||||
# ETCD 加密算法
|
||||
Algorithm: AES-CBC
|
||||
Key Size: 256 bits (32 bytes)
|
||||
Encoding: Base64
|
||||
|
||||
# Secret 挂载方式
|
||||
Method: volumeMount (read-only)
|
||||
或
|
||||
Method: valueFrom.secretKeyRef
|
||||
```
|
||||
|
||||
## 常见问题速查
|
||||
|
||||
| 问题 | 排查命令 | 解决方案 |
|
||||
|-----|--------|--------|
|
||||
| Pod 无法启动 | `kubectl describe pod` | 检查 Secret/RBAC |
|
||||
| 权限被拒绝 | `kubectl auth can-i get secrets` | 验证 RBAC 绑定 |
|
||||
| Redis 连接失败 | `redis-cli PING` | 检查 Redis Pods |
|
||||
| JWT 验证失败 | 查看 Pod 日志 | 检查 Redis 中的令牌 |
|
||||
| CSRF 验证失败 | 查看 Envoy 日志 | 检查 Cookie/Header 匹配 |
|
||||
| ETCD 加密失败 | `kubectl get secret` | 检查 kube-apiserver 启动参数 |
|
||||
|
||||
## 部署检查清单 (5分钟版)
|
||||
|
||||
```bash
|
||||
# 第1步: 部署 Secret (10秒)
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml && sleep 5
|
||||
|
||||
# 第2步: 验证 Secret (10秒)
|
||||
kubectl get secret jwt-secret -n juwan && echo "✓ Secret 已创建"
|
||||
|
||||
# 第3步: 验证 RBAC (10秒)
|
||||
kubectl get role jwt-secret-reader -n juwan && echo "✓ RBAC 已配置"
|
||||
|
||||
# 第4步: 更新 Deployments (20秒)
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 第5步: 等待 Pods 启动 (30秒)
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 第6步: 快速功能测试 (2分钟)
|
||||
# 创建一个令牌并验证可读取
|
||||
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:*"
|
||||
```
|
||||
|
||||
## 密钥轮换步骤
|
||||
|
||||
```bash
|
||||
# 1. 生成新密钥
|
||||
NEW_KEY=$(head -c 32 /dev/urandom | base64)
|
||||
|
||||
# 2. 更新 Secret
|
||||
kubectl create secret generic jwt-secret \
|
||||
--from-literal=secret-key=$NEW_KEY \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 3. 重启 Pods(自动挂载新 Secret)
|
||||
kubectl rollout restart deployment/user-rpc -n juwan
|
||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
||||
|
||||
# 4. 等待 Pods 启动
|
||||
kubectl rollout status deployment/user-rpc -n juwan
|
||||
kubectl rollout status deployment/envoy-gateway -n juwan
|
||||
|
||||
# 5. 旧令牌现在需要刷新或重新登录
|
||||
```
|
||||
|
||||
## 文档地图
|
||||
|
||||
```
|
||||
deploy/k8s/secrets/
|
||||
├── jwt-secret.yaml ← Secrets + RBAC 配置
|
||||
├── README.md ← 开始阅读(快速指南)
|
||||
├── SUMMARY.md ← 本文件(系统概览)
|
||||
├── DEPLOYMENT.md ← 详细部署步骤(12步)
|
||||
├── ENCRYPTION.md ← ETCD 加密详细指南
|
||||
├── VERIFICATION.md ← 完整验证清单(12部分)
|
||||
└── QUICK_REFERENCE.md ← 本快速参考卡片
|
||||
|
||||
app/users/api/
|
||||
└── INTEGRATION.md ← JWT 代码集成指南
|
||||
|
||||
app/users/rpc/
|
||||
├── internal/utils/jwt.go ← JwtManager 实现
|
||||
├── internal/config/config.go ← JWT 配置
|
||||
├── internal/svc/serviceContext.go ← 依赖注入
|
||||
└── etc/pb.yaml ← 运行时配置
|
||||
```
|
||||
|
||||
## 关键时间点
|
||||
|
||||
| 阶段 | 时间 | 操作 |
|
||||
|-----|------|------|
|
||||
| 令牌签发 | T0 | 生成 JWT,过期时间 = T0 + 7天 |
|
||||
| | | 在 Redis 存储,TTL = 30天 |
|
||||
| Token 过期 | T0 + 7天 | JWT 验证失败 |
|
||||
| 令牌刷新 | T0 + 7天到T0 + 30天 | 如果 Redis 仍有数据,生成新令牌 |
|
||||
| 完全失效 | T0 + 30天 | Redis 删除,无法再刷新 |
|
||||
| 重新登录 | T0 + 30天+ | 用户需要重新登录 |
|
||||
|
||||
## 性能提示
|
||||
|
||||
```bash
|
||||
# 高并发下优化 Redis 连接
|
||||
# 在 pb.yaml 中调整:
|
||||
CacheConf:
|
||||
- Host: "user-redis.juwan:6379"
|
||||
Type: "cluster"
|
||||
MaxConnections: 100
|
||||
ConnectionPoolSize: 50
|
||||
|
||||
# 监控 JWT 验证吞吐量
|
||||
# 在 Prometheus 查询:
|
||||
rate(jwt_validations_total[5m])
|
||||
rate(jwt_refresh_total[5m])
|
||||
```
|
||||
|
||||
## 安全提示
|
||||
|
||||
✅ **必做**
|
||||
- [ ] 定期轮换 JWT 秘钥(季度)
|
||||
- [ ] 定期轮换 ETCD 加密密钥(年度)
|
||||
- [ ] 备份加密密钥到安全位置
|
||||
- [ ] 启用审计日志
|
||||
- [ ] 监控异常的令牌验证失败
|
||||
|
||||
❌ **禁止**
|
||||
- [ ] 不要在日志中输出 JWT 秘钥
|
||||
- [ ] 不要在代码库中存储密钥
|
||||
- [ ] 不要发送明文密钥到 Slack/Email
|
||||
- [ ] 不要在多个环境间共享密钥
|
||||
|
||||
## 版本信息
|
||||
|
||||
```
|
||||
Kubernetes: 1.24+
|
||||
Go-zero: v1.10.0+
|
||||
Redis: 7.0+
|
||||
ETCD: 3.5+
|
||||
Envoy: v1.32.2+
|
||||
```
|
||||
|
||||
## 支持和反馈
|
||||
|
||||
遇到问题?按优先级检查:
|
||||
|
||||
1. **运行验证脚本**
|
||||
```bash
|
||||
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
./deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
2. **查看日志**
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
```
|
||||
|
||||
3. **阅读 VERIFICATION.md**
|
||||
- 第1-5部分: 基础配置
|
||||
- 第6-8部分: 网络和监控
|
||||
- 第9部分: ETCD 加密
|
||||
- 第11部分: 故障排查
|
||||
|
||||
4. **详细指南**
|
||||
- DEPLOYMENT.md - 完整步骤
|
||||
- INTEGRATION.md - 代码集成
|
||||
- ENCRYPTION.md - 加密配置
|
||||
|
||||
+148
-148
@@ -1,148 +1,148 @@
|
||||
# JWT Secret Management
|
||||
|
||||
This directory contains secure configuration for JWT secret key management.
|
||||
|
||||
## Files
|
||||
|
||||
- `jwt-secret.yaml`: Kubernetes Secret + ServiceAccount + RBAC rules
|
||||
- `ENCRYPTION.md`: Guide for enabling ETCD static encryption at rest
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create the Secret and RBAC
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
This will create:
|
||||
- Secret `jwt-secret` in namespace `juwan` containing the JWT secret key
|
||||
- ServiceAccount `user-rpc` in namespace `juwan`
|
||||
- ServiceAccount `envoy-gateway` in namespace `juwan`
|
||||
- Role `jwt-secret-reader` that allows reading only `jwt-secret`
|
||||
- RoleBindings to grant both ServiceAccounts read permission on the secret
|
||||
|
||||
### 2. Update user-rpc Deployment
|
||||
|
||||
Update `deploy/k8s/service/user/user-rpc.yaml` to:
|
||||
|
||||
1. Set the serviceAccountName:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: user-rpc
|
||||
```
|
||||
|
||||
2. Add environment variable to load JWT secret:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: user-rpc
|
||||
env:
|
||||
- name: JWT_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: jwt-secret
|
||||
key: secret-key
|
||||
```
|
||||
|
||||
### 3. Update envoy-gateway Deployment
|
||||
|
||||
Update `deploy/k8s/envoy/envoy.yaml` to:
|
||||
|
||||
1. Set the serviceAccountName:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: envoy-gateway
|
||||
```
|
||||
|
||||
2. Add environment variable or mount Secret:
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: jwt-secret
|
||||
mountPath: /etc/jwt
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
- name: jwt-secret
|
||||
secret:
|
||||
secretName: jwt-secret
|
||||
defaultMode: 0400
|
||||
```
|
||||
|
||||
Then reference it in the Envoy config:
|
||||
```yaml
|
||||
data:
|
||||
envoy.yaml: |
|
||||
# Read JWT secret from /etc/jwt/secret-key
|
||||
```
|
||||
|
||||
### 4. Enable ETCD Encryption
|
||||
|
||||
Follow the guide in `ENCRYPTION.md` to enable static encryption at rest for all secrets in ETCD.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Least Privilege
|
||||
|
||||
- Only `user-rpc` and `envoy-gateway` can read the JWT secret
|
||||
- No other services or users have access
|
||||
- The Role allows reading **only** the `jwt-secret`, not other secrets
|
||||
|
||||
### Encryption at Rest
|
||||
|
||||
- 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
|
||||
|
||||
### Secret Rotation
|
||||
|
||||
To rotate the JWT secret key:
|
||||
|
||||
1. Generate a new key
|
||||
2. Update the Secret:
|
||||
```bash
|
||||
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
|
||||
4. Old tokens will become invalid (you may need to log users out)
|
||||
|
||||
## Production Checklist
|
||||
|
||||
- [ ] ETCD encryption enabled (see ENCRYPTION.md)
|
||||
- [ ] JWT secret key changed from default
|
||||
- [ ] Both user-rpc and envoy-gateway Deployments use correct serviceAccountName
|
||||
- [ ] Both Deployments load the secret via environment variable or volume mount
|
||||
- [ ] Regular secret rotation policy implemented
|
||||
- [ ] Secret backup stored in secure location (encrypted)
|
||||
- [ ] RBAC audit logging enabled to track secret access
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot read jwt-secret
|
||||
Check if the Pod is using the correct ServiceAccount:
|
||||
```bash
|
||||
kubectl get deployment user-rpc -o yaml | grep serviceAccountName
|
||||
```
|
||||
|
||||
### Secret not being mounted
|
||||
Verify the Secret exists:
|
||||
```bash
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
```
|
||||
|
||||
Check Pod logs for mounting errors:
|
||||
```bash
|
||||
kubectl logs -l app=user-rpc -n juwan
|
||||
```
|
||||
|
||||
### Permission denied error
|
||||
Verify RBAC binding:
|
||||
```bash
|
||||
kubectl get rolebinding -n juwan
|
||||
kubectl get role jwt-secret-reader -n juwan
|
||||
```
|
||||
# JWT Secret Management
|
||||
|
||||
This directory contains secure configuration for JWT secret key management.
|
||||
|
||||
## Files
|
||||
|
||||
- `jwt-secret.yaml`: Kubernetes Secret + ServiceAccount + RBAC rules
|
||||
- `ENCRYPTION.md`: Guide for enabling ETCD static encryption at rest
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create the Secret and RBAC
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
This will create:
|
||||
- Secret `jwt-secret` in namespace `juwan` containing the JWT secret key
|
||||
- ServiceAccount `user-rpc` in namespace `juwan`
|
||||
- ServiceAccount `envoy-gateway` in namespace `juwan`
|
||||
- Role `jwt-secret-reader` that allows reading only `jwt-secret`
|
||||
- RoleBindings to grant both ServiceAccounts read permission on the secret
|
||||
|
||||
### 2. Update user-rpc Deployment
|
||||
|
||||
Update `deploy/k8s/service/user/user-rpc.yaml` to:
|
||||
|
||||
1. Set the serviceAccountName:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: user-rpc
|
||||
```
|
||||
|
||||
2. Add environment variable to load JWT secret:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: user-rpc
|
||||
env:
|
||||
- name: JWT_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: jwt-secret
|
||||
key: secret-key
|
||||
```
|
||||
|
||||
### 3. Update envoy-gateway Deployment
|
||||
|
||||
Update `deploy/k8s/envoy/envoy.yaml` to:
|
||||
|
||||
1. Set the serviceAccountName:
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: envoy-gateway
|
||||
```
|
||||
|
||||
2. Add environment variable or mount Secret:
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: jwt-secret
|
||||
mountPath: /etc/jwt
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
- name: jwt-secret
|
||||
secret:
|
||||
secretName: jwt-secret
|
||||
defaultMode: 0400
|
||||
```
|
||||
|
||||
Then reference it in the Envoy config:
|
||||
```yaml
|
||||
data:
|
||||
envoy.yaml: |
|
||||
# Read JWT secret from /etc/jwt/secret-key
|
||||
```
|
||||
|
||||
### 4. Enable ETCD Encryption
|
||||
|
||||
Follow the guide in `ENCRYPTION.md` to enable static encryption at rest for all secrets in ETCD.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Least Privilege
|
||||
|
||||
- Only `user-rpc` and `envoy-gateway` can read the JWT secret
|
||||
- No other services or users have access
|
||||
- The Role allows reading **only** the `jwt-secret`, not other secrets
|
||||
|
||||
### Encryption at Rest
|
||||
|
||||
- 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
|
||||
|
||||
### Secret Rotation
|
||||
|
||||
To rotate the JWT secret key:
|
||||
|
||||
1. Generate a new key
|
||||
2. Update the Secret:
|
||||
```bash
|
||||
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
|
||||
4. Old tokens will become invalid (you may need to log users out)
|
||||
|
||||
## Production Checklist
|
||||
|
||||
- [ ] ETCD encryption enabled (see ENCRYPTION.md)
|
||||
- [ ] JWT secret key changed from default
|
||||
- [ ] Both user-rpc and envoy-gateway Deployments use correct serviceAccountName
|
||||
- [ ] Both Deployments load the secret via environment variable or volume mount
|
||||
- [ ] Regular secret rotation policy implemented
|
||||
- [ ] Secret backup stored in secure location (encrypted)
|
||||
- [ ] RBAC audit logging enabled to track secret access
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot read jwt-secret
|
||||
Check if the Pod is using the correct ServiceAccount:
|
||||
```bash
|
||||
kubectl get deployment user-rpc -o yaml | grep serviceAccountName
|
||||
```
|
||||
|
||||
### Secret not being mounted
|
||||
Verify the Secret exists:
|
||||
```bash
|
||||
kubectl get secret jwt-secret -n juwan
|
||||
```
|
||||
|
||||
Check Pod logs for mounting errors:
|
||||
```bash
|
||||
kubectl logs -l app=user-rpc -n juwan
|
||||
```
|
||||
|
||||
### Permission denied error
|
||||
Verify RBAC binding:
|
||||
```bash
|
||||
kubectl get rolebinding -n juwan
|
||||
kubectl get role jwt-secret-reader -n juwan
|
||||
```
|
||||
|
||||
+366
-366
@@ -1,366 +1,366 @@
|
||||
# JWT 认证系统 + ETCD 加密 - 完整部署总结
|
||||
|
||||
## 项目概览
|
||||
|
||||
这个项目为微服务提供了一个完整的 JWT 认证系统,包括:
|
||||
|
||||
1. **JWT 令牌管理** - 令牌生成、验证、刷新和撤销
|
||||
2. **Redis Cluster 存储** - 令牌交换缓存(30天TTL)和用户会话管理
|
||||
3. **RBAC 权限控制** - 限制只有 user-rpc 和 envoy-gateway 服务可以访问 JWT 秘钥
|
||||
4. **ETCD 加密** - 在 Kubernetes 集群中对所有 Secrets 进行加密
|
||||
5. **网关保护** - Envoy 网关处理 CSRF 防护和请求路由
|
||||
|
||||
## 创建的文件清单
|
||||
|
||||
### 部署配置文件
|
||||
|
||||
#### `/deploy/k8s/secrets/`
|
||||
|
||||
| 文件 | 说明 | 关键内容 |
|
||||
|-----|------|--------|
|
||||
| `jwt-secret.yaml` | Secret + RBAC 配置 | 包含JWT秘钥、ServiceAccounts、Role、RoleBindings |
|
||||
| `README.md` | 快速参考指南 | Secret 创建和 Deployment 更新说明 |
|
||||
| `DEPLOYMENT.md` | 详细部署步骤 | 12个部署步骤,包括ETCD加密配置 |
|
||||
| `ENCRYPTION.md` | ETCD加密完整指南 | 密钥生成、配置修改、验证流程 |
|
||||
| `VERIFICATION.md` | 验证清单 | 12个部分的完整验证脚本和检查项 |
|
||||
|
||||
### 应用代码更新
|
||||
|
||||
| 文件路径 | 修改内容 |
|
||||
|---------|--------|
|
||||
| `/app/users/rpc/internal/utils/jwt.go` | JwtManager 实现(已存在) |
|
||||
| `/app/users/rpc/internal/config/config.go` | JwtConfig 结构体(已存在) |
|
||||
| `/app/users/rpc/internal/svc/serviceContext.go` | Redis Cluster + JwtManager 依赖注入(已存在) |
|
||||
| `/app/users/rpc/etc/pb.yaml` | JWT 和 Redis Cluster 配置(已存在) |
|
||||
| `/deploy/k8s/service/user/user-rpc.yaml` | ✅ **已更新** - 添加 serviceAccountName + JWT_SECRET_KEY 环境变量 |
|
||||
| `/deploy/k8s/envoy/envoy.yaml` | ✅ **已更新** - 添加 serviceAccountName: envoy-gateway |
|
||||
| `/app/users/api/INTEGRATION.md` | 🆕 **新建** - JWT 集成指南(interceptors, handlers, middleware) |
|
||||
|
||||
## 部署状态示意图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Kubernetes Cluster │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ juwan Namespace │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Secret: jwt-secret │ │ │
|
||||
│ │ │ ├─ secret-key: <encrypted in ETCD> │ │ │
|
||||
│ │ │ └─ Protected by RBAC Role + RoleBindings │ │ │
|
||||
│ │ └────────────────────────────────────────────────────┘ │ │
|
||||
│ │ △ │ │
|
||||
│ │ │ (mounted via serviceAccountName) │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────────────┐ ┌──────────────────────┐ │ │
|
||||
│ │ │ user-rpc │ │ envoy-gateway │ │ │
|
||||
│ │ │ Deployment │ │ Deployment │ │ │
|
||||
│ │ ├─────────────────┤ ├──────────────────────┤ │ │
|
||||
│ │ │ SA: user-rpc │ │ SA: envoy-gateway │ │ │
|
||||
│ │ │ Replicas: 3 │ │ Replicas: 1 │ │ │
|
||||
│ │ │ Port: 9001(RPC) │ │ Port: 8080(HTTP) │ │ │
|
||||
│ │ │ 4001(Met) │ │ │ │ │
|
||||
│ │ └─────────────────┘ └──────────────────────┘ │ │
|
||||
│ │ │ JWT Manager │ CSRF Filter │ │
|
||||
│ │ │ (HS256 signing) │ (X-CSRF-Token) │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ┌──────▼──────────────────────────▼─────┐ │ │
|
||||
│ │ │ user-redis (RedisCluster) │ │ │
|
||||
│ │ │ 3-node cluster │ │ │
|
||||
│ │ │ - Token exchange cache (30d TTL) │ │ │
|
||||
│ │ │ - User session management │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Control Plane (kube-apiserver) │ │
|
||||
│ │ • ETCD 加密: AES-CBC 32-byte key │ │
|
||||
│ │ • Secrets 自动加密存储 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 核心配置参数
|
||||
|
||||
### JWT 配置
|
||||
|
||||
```yaml
|
||||
JWT:
|
||||
SecretKey: "your-secret-jwt-key-change-this-in-production"
|
||||
Issuer: "your-app-name"
|
||||
# Token 有效期: 7 天
|
||||
# Redis TTL: 30 天(支持令牌刷新)
|
||||
```
|
||||
|
||||
### Redis Cluster
|
||||
|
||||
```yaml
|
||||
RedisCluster:
|
||||
ClusterSize: 3 🔴 主服务器 + 2 🔵 从服务器
|
||||
Address: "user-redis.juwan:6379"
|
||||
HighAvailability: 自动故障转移
|
||||
```
|
||||
|
||||
### RBAC 权限
|
||||
|
||||
```yaml
|
||||
Role: jwt-secret-reader
|
||||
Resources: [secrets]
|
||||
ResourceNames: [jwt-secret]
|
||||
Verbs: [get] # 只读,无列表/创建/删除
|
||||
Subjects:
|
||||
- user-rpc (ServiceAccount)
|
||||
- envoy-gateway (ServiceAccount)
|
||||
```
|
||||
|
||||
## 部署流程
|
||||
|
||||
### 快速部署(5分钟)
|
||||
|
||||
```bash
|
||||
# 第1步:创建 Secret 和 RBAC
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
|
||||
# 第2步:更新 Deployments
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 第3步:验证
|
||||
./verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
### ETCD 加密部署(需要集群管理员权限)
|
||||
|
||||
```bash
|
||||
# 在 Control Plane 节点执行
|
||||
1. 生成 32 字节密钥
|
||||
2. 创建 /etc/kubernetes/encryption-config.yaml
|
||||
3. 修改 /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
4. 重启 kube-apiserver
|
||||
5. 验证加密已启用
|
||||
```
|
||||
|
||||
详见:`deploy/k8s/secrets/ENCRYPTION.md`
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. JWT 令牌生命周期
|
||||
|
||||
```
|
||||
登录 → 生成 JWT
|
||||
├─ 有效期: 7 天(exp claim)
|
||||
└─ 存储到 Redis: 30 天(TTL)
|
||||
|
||||
Token 过期(7天+)
|
||||
├─ JWT 签名验证失败
|
||||
└─ 检查 Redis 是否仍有数据
|
||||
├─ 有 → 生成新 Token(刷新)✅
|
||||
└─ 无 → 令牌已过期,需要重新登录 ❌
|
||||
```
|
||||
|
||||
### 2. Redis 双键结构
|
||||
|
||||
```
|
||||
jwt:user:{userId} → {token}
|
||||
用途: 快速查询用户当前令牌
|
||||
TTL: 30 天
|
||||
|
||||
jwt:token:{token} → {payload}
|
||||
用途: 令牌验证和刷新
|
||||
TTL: 30 天
|
||||
```
|
||||
|
||||
### 3. CSRF 防护(Envoy 网关)
|
||||
|
||||
```
|
||||
安全方法 (GET/HEAD/OPTIONS)
|
||||
→ 自动生成 csrf_token
|
||||
→ 返回 Set-Cookie: csrf_token=...
|
||||
|
||||
不安全方法 (POST/PUT/DELETE/PATCH)
|
||||
→ 检查 Cookie csrf_token
|
||||
→ 检查 X-CSRF-Token 头
|
||||
→ 两者必须相等,否则 403
|
||||
```
|
||||
|
||||
### 4. 权限隔离
|
||||
|
||||
```
|
||||
Only user-rpc + envoy-gateway 可以:
|
||||
✅ 读 jwt-secret
|
||||
|
||||
Other services 无法:
|
||||
❌ 列出 secrets
|
||||
❌ 获取 jwt-secret(RBAC 拒绝)
|
||||
❌ 删除 secrets
|
||||
```
|
||||
|
||||
### 5. ETCD 加密
|
||||
|
||||
```
|
||||
未加密:
|
||||
etcdctl get /registry/seca/...
|
||||
→ secret-key: "plaintext-value"
|
||||
|
||||
已加密 (AES-CBC):
|
||||
etcdctl get /registry/secrets/...
|
||||
→ 二进制数据,无法读取
|
||||
```
|
||||
|
||||
## 集成点
|
||||
|
||||
### 1. RPC Handler(需要实现)
|
||||
|
||||
```go
|
||||
// 在 gRPC server 中注册拦截器
|
||||
s := grpc.NewServer(
|
||||
grpc.UnaryInterceptor(interceptor.JwtUnaryInterceptor(ctx)),
|
||||
)
|
||||
|
||||
// 拦截器会:
|
||||
// 1. 提取 Authorization 头中的 Token
|
||||
// 2. 调用 JwtManager.Valid()
|
||||
// 3. 如果过期,尝试 JwtManager.Renew()
|
||||
// 4. 将声明注入 context
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第1-2章
|
||||
|
||||
### 2. REST Endpoint(需要实现)
|
||||
|
||||
```go
|
||||
// 创建 JWT Middleware
|
||||
protected := middleware.JwtMiddleware(svcCtx)
|
||||
|
||||
// 应用到受保护的路由
|
||||
router.HandleFunc("GET /api/v1/users/me",
|
||||
protected(user.GetUserInfoHandler(svcCtx)))
|
||||
|
||||
// Middleware 会验证 Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第3-4章
|
||||
|
||||
### 3. 登录/登出流程(需要实现)
|
||||
|
||||
```
|
||||
登录:
|
||||
1. 验证用户凭证(DB 查询)
|
||||
2. JwtManager.New() → 生成令牌
|
||||
3. 返回令牌给客户端
|
||||
|
||||
登出:
|
||||
1. 从上下文提取 userId
|
||||
2. JwtManager.Revoke() → 删除 Redis 中的令牌
|
||||
3. 用户需要重新登录获取新令牌
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第5-6章
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 场景 | 推荐阅读 |
|
||||
|-----|--------|
|
||||
| 第一次部署 | `README.md` → `DEPLOYMENT.md` |
|
||||
| 部署遇到问题 | `VERIFICATION.md` + `DEPLOYMENT.md` 故障排查部分 |
|
||||
| 代码集成 | `app/users/api/INTEGRATION.md` |
|
||||
| ETCD 加密配置 | `ENCRYPTION.md` |
|
||||
| ETCD 加密验证 | `VERIFICATION.md` 第9部分 |
|
||||
| 安全最佳实践 | `DEPLOYMENT.md` 安全最佳实践部分 |
|
||||
| 灾难恢复 | `DEPLOYMENT.md` 灾难恢复部分 |
|
||||
|
||||
## 生产就绪检查清单
|
||||
|
||||
- [ ] 所有 Pods 都在 Running 状态
|
||||
- [ ] JWT Secret 已创建并正确挂载
|
||||
- [ ] RBAC 权限验证通过
|
||||
- [ ] Redis Cluster 健康(3/3 节点)
|
||||
- [ ] ETCD 加密已启用(如需要)
|
||||
- [ ] 监控和日志聚合正常工作
|
||||
- [ ] 密钥轮换计划已制定
|
||||
- [ ] 备份和恢复流程已文档化
|
||||
- [ ] 安全审计日志已启用
|
||||
- [ ] 端到端测试已通过
|
||||
|
||||
## 下一步行动
|
||||
|
||||
### 短期(本周)
|
||||
|
||||
1. **部署 Secret 和 RBAC**
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
2. **更新 Deployments**
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
3. **验证部署**
|
||||
```bash
|
||||
./verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
### 中期(本月)
|
||||
|
||||
1. **实现 JWT 集成**
|
||||
- 创建 gRPC 拦截器
|
||||
- 实现登录/登出端点
|
||||
- 添加 JWT 中间件到 REST API
|
||||
|
||||
2. **端到端测试**
|
||||
- 测试令牌生成和验证
|
||||
- 测试令牌刷新
|
||||
- 测试 CSRF 防护
|
||||
|
||||
### 长期(本季度)
|
||||
|
||||
1. **启用 ETCD 加密**
|
||||
- 按照 `ENCRYPTION.md` 配置
|
||||
- 验证所有 Secrets 都已加密
|
||||
|
||||
2. **生产部署**
|
||||
- 启用审计日志
|
||||
- 配置监控和告警
|
||||
- 制定密钥轮换政策
|
||||
|
||||
## 支持
|
||||
|
||||
如遇到问题:
|
||||
|
||||
1. **检查日志**
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
2. **运行验证脚本**
|
||||
```bash
|
||||
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
./deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
3. **查看详细文档**
|
||||
- 部署问题 → `DEPLOYMENT.md`
|
||||
- 代码集成 → `INTEGRATION.md`
|
||||
- ETCD 加密 → `ENCRYPTION.md`
|
||||
- 诊断 → `VERIFICATION.md`
|
||||
|
||||
## 总结
|
||||
|
||||
这个系统为微服务提供了:
|
||||
|
||||
✅ **安全的身份验证** - JWT 令牌 + HS256 签名
|
||||
✅ **灵活的令牌管理** - 7天有效期,30天可刷新
|
||||
✅ **高可用性** - Redis Cluster 自动故障转移
|
||||
✅ **权限隔离** - RBAC 限制密钥访问
|
||||
✅ **数据加密** - ETCD 加密保护敏感信息
|
||||
✅ **请求保护** - Envoy CSRF 双令牌验证
|
||||
|
||||
现在可以部署并集成到应用中了!
|
||||
# JWT 认证系统 + ETCD 加密 - 完整部署总结
|
||||
|
||||
## 项目概览
|
||||
|
||||
这个项目为微服务提供了一个完整的 JWT 认证系统,包括:
|
||||
|
||||
1. **JWT 令牌管理** - 令牌生成、验证、刷新和撤销
|
||||
2. **Redis Cluster 存储** - 令牌交换缓存(30天TTL)和用户会话管理
|
||||
3. **RBAC 权限控制** - 限制只有 user-rpc 和 envoy-gateway 服务可以访问 JWT 秘钥
|
||||
4. **ETCD 加密** - 在 Kubernetes 集群中对所有 Secrets 进行加密
|
||||
5. **网关保护** - Envoy 网关处理 CSRF 防护和请求路由
|
||||
|
||||
## 创建的文件清单
|
||||
|
||||
### 部署配置文件
|
||||
|
||||
#### `/deploy/k8s/secrets/`
|
||||
|
||||
| 文件 | 说明 | 关键内容 |
|
||||
|-----|------|--------|
|
||||
| `jwt-secret.yaml` | Secret + RBAC 配置 | 包含JWT秘钥、ServiceAccounts、Role、RoleBindings |
|
||||
| `README.md` | 快速参考指南 | Secret 创建和 Deployment 更新说明 |
|
||||
| `DEPLOYMENT.md` | 详细部署步骤 | 12个部署步骤,包括ETCD加密配置 |
|
||||
| `ENCRYPTION.md` | ETCD加密完整指南 | 密钥生成、配置修改、验证流程 |
|
||||
| `VERIFICATION.md` | 验证清单 | 12个部分的完整验证脚本和检查项 |
|
||||
|
||||
### 应用代码更新
|
||||
|
||||
| 文件路径 | 修改内容 |
|
||||
|---------|--------|
|
||||
| `/app/users/rpc/internal/utils/jwt.go` | JwtManager 实现(已存在) |
|
||||
| `/app/users/rpc/internal/config/config.go` | JwtConfig 结构体(已存在) |
|
||||
| `/app/users/rpc/internal/svc/serviceContext.go` | Redis Cluster + JwtManager 依赖注入(已存在) |
|
||||
| `/app/users/rpc/etc/pb.yaml` | JWT 和 Redis Cluster 配置(已存在) |
|
||||
| `/deploy/k8s/service/user/user-rpc.yaml` | ✅ **已更新** - 添加 serviceAccountName + JWT_SECRET_KEY 环境变量 |
|
||||
| `/deploy/k8s/envoy/envoy.yaml` | ✅ **已更新** - 添加 serviceAccountName: envoy-gateway |
|
||||
| `/app/users/api/INTEGRATION.md` | 🆕 **新建** - JWT 集成指南(interceptors, handlers, middleware) |
|
||||
|
||||
## 部署状态示意图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Kubernetes Cluster │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ juwan Namespace │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Secret: jwt-secret │ │ │
|
||||
│ │ │ ├─ secret-key: <encrypted in ETCD> │ │ │
|
||||
│ │ │ └─ Protected by RBAC Role + RoleBindings │ │ │
|
||||
│ │ └────────────────────────────────────────────────────┘ │ │
|
||||
│ │ △ │ │
|
||||
│ │ │ (mounted via serviceAccountName) │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────────────┐ ┌──────────────────────┐ │ │
|
||||
│ │ │ user-rpc │ │ envoy-gateway │ │ │
|
||||
│ │ │ Deployment │ │ Deployment │ │ │
|
||||
│ │ ├─────────────────┤ ├──────────────────────┤ │ │
|
||||
│ │ │ SA: user-rpc │ │ SA: envoy-gateway │ │ │
|
||||
│ │ │ Replicas: 3 │ │ Replicas: 1 │ │ │
|
||||
│ │ │ Port: 9001(RPC) │ │ Port: 8080(HTTP) │ │ │
|
||||
│ │ │ 4001(Met) │ │ │ │ │
|
||||
│ │ └─────────────────┘ └──────────────────────┘ │ │
|
||||
│ │ │ JWT Manager │ CSRF Filter │ │
|
||||
│ │ │ (HS256 signing) │ (X-CSRF-Token) │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ┌──────▼──────────────────────────▼─────┐ │ │
|
||||
│ │ │ user-redis (RedisCluster) │ │ │
|
||||
│ │ │ 3-node cluster │ │ │
|
||||
│ │ │ - Token exchange cache (30d TTL) │ │ │
|
||||
│ │ │ - User session management │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Control Plane (kube-apiserver) │ │
|
||||
│ │ • ETCD 加密: AES-CBC 32-byte key │ │
|
||||
│ │ • Secrets 自动加密存储 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 核心配置参数
|
||||
|
||||
### JWT 配置
|
||||
|
||||
```yaml
|
||||
JWT:
|
||||
SecretKey: "your-secret-jwt-key-change-this-in-production"
|
||||
Issuer: "your-app-name"
|
||||
# Token 有效期: 7 天
|
||||
# Redis TTL: 30 天(支持令牌刷新)
|
||||
```
|
||||
|
||||
### Redis Cluster
|
||||
|
||||
```yaml
|
||||
RedisCluster:
|
||||
ClusterSize: 3 🔴 主服务器 + 2 🔵 从服务器
|
||||
Address: "user-redis.juwan:6379"
|
||||
HighAvailability: 自动故障转移
|
||||
```
|
||||
|
||||
### RBAC 权限
|
||||
|
||||
```yaml
|
||||
Role: jwt-secret-reader
|
||||
Resources: [secrets]
|
||||
ResourceNames: [jwt-secret]
|
||||
Verbs: [get] # 只读,无列表/创建/删除
|
||||
Subjects:
|
||||
- user-rpc (ServiceAccount)
|
||||
- envoy-gateway (ServiceAccount)
|
||||
```
|
||||
|
||||
## 部署流程
|
||||
|
||||
### 快速部署(5分钟)
|
||||
|
||||
```bash
|
||||
# 第1步:创建 Secret 和 RBAC
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
|
||||
# 第2步:更新 Deployments
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
|
||||
# 第3步:验证
|
||||
./verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
### ETCD 加密部署(需要集群管理员权限)
|
||||
|
||||
```bash
|
||||
# 在 Control Plane 节点执行
|
||||
1. 生成 32 字节密钥
|
||||
2. 创建 /etc/kubernetes/encryption-config.yaml
|
||||
3. 修改 /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
4. 重启 kube-apiserver
|
||||
5. 验证加密已启用
|
||||
```
|
||||
|
||||
详见:`deploy/k8s/secrets/ENCRYPTION.md`
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. JWT 令牌生命周期
|
||||
|
||||
```
|
||||
登录 → 生成 JWT
|
||||
├─ 有效期: 7 天(exp claim)
|
||||
└─ 存储到 Redis: 30 天(TTL)
|
||||
|
||||
Token 过期(7天+)
|
||||
├─ JWT 签名验证失败
|
||||
└─ 检查 Redis 是否仍有数据
|
||||
├─ 有 → 生成新 Token(刷新)✅
|
||||
└─ 无 → 令牌已过期,需要重新登录 ❌
|
||||
```
|
||||
|
||||
### 2. Redis 双键结构
|
||||
|
||||
```
|
||||
jwt:user:{userId} → {token}
|
||||
用途: 快速查询用户当前令牌
|
||||
TTL: 30 天
|
||||
|
||||
jwt:token:{token} → {payload}
|
||||
用途: 令牌验证和刷新
|
||||
TTL: 30 天
|
||||
```
|
||||
|
||||
### 3. CSRF 防护(Envoy 网关)
|
||||
|
||||
```
|
||||
安全方法 (GET/HEAD/OPTIONS)
|
||||
→ 自动生成 csrf_token
|
||||
→ 返回 Set-Cookie: csrf_token=...
|
||||
|
||||
不安全方法 (POST/PUT/DELETE/PATCH)
|
||||
→ 检查 Cookie csrf_token
|
||||
→ 检查 X-CSRF-Token 头
|
||||
→ 两者必须相等,否则 403
|
||||
```
|
||||
|
||||
### 4. 权限隔离
|
||||
|
||||
```
|
||||
Only user-rpc + envoy-gateway 可以:
|
||||
✅ 读 jwt-secret
|
||||
|
||||
Other services 无法:
|
||||
❌ 列出 secrets
|
||||
❌ 获取 jwt-secret(RBAC 拒绝)
|
||||
❌ 删除 secrets
|
||||
```
|
||||
|
||||
### 5. ETCD 加密
|
||||
|
||||
```
|
||||
未加密:
|
||||
etcdctl get /registry/seca/...
|
||||
→ secret-key: "plaintext-value"
|
||||
|
||||
已加密 (AES-CBC):
|
||||
etcdctl get /registry/secrets/...
|
||||
→ 二进制数据,无法读取
|
||||
```
|
||||
|
||||
## 集成点
|
||||
|
||||
### 1. RPC Handler(需要实现)
|
||||
|
||||
```go
|
||||
// 在 gRPC server 中注册拦截器
|
||||
s := grpc.NewServer(
|
||||
grpc.UnaryInterceptor(interceptor.JwtUnaryInterceptor(ctx)),
|
||||
)
|
||||
|
||||
// 拦截器会:
|
||||
// 1. 提取 Authorization 头中的 Token
|
||||
// 2. 调用 JwtManager.Valid()
|
||||
// 3. 如果过期,尝试 JwtManager.Renew()
|
||||
// 4. 将声明注入 context
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第1-2章
|
||||
|
||||
### 2. REST Endpoint(需要实现)
|
||||
|
||||
```go
|
||||
// 创建 JWT Middleware
|
||||
protected := middleware.JwtMiddleware(svcCtx)
|
||||
|
||||
// 应用到受保护的路由
|
||||
router.HandleFunc("GET /api/v1/users/me",
|
||||
protected(user.GetUserInfoHandler(svcCtx)))
|
||||
|
||||
// Middleware 会验证 Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第3-4章
|
||||
|
||||
### 3. 登录/登出流程(需要实现)
|
||||
|
||||
```
|
||||
登录:
|
||||
1. 验证用户凭证(DB 查询)
|
||||
2. JwtManager.New() → 生成令牌
|
||||
3. 返回令牌给客户端
|
||||
|
||||
登出:
|
||||
1. 从上下文提取 userId
|
||||
2. JwtManager.Revoke() → 删除 Redis 中的令牌
|
||||
3. 用户需要重新登录获取新令牌
|
||||
```
|
||||
|
||||
参考:`app/users/api/INTEGRATION.md` 第5-6章
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 场景 | 推荐阅读 |
|
||||
|-----|--------|
|
||||
| 第一次部署 | `README.md` → `DEPLOYMENT.md` |
|
||||
| 部署遇到问题 | `VERIFICATION.md` + `DEPLOYMENT.md` 故障排查部分 |
|
||||
| 代码集成 | `app/users/api/INTEGRATION.md` |
|
||||
| ETCD 加密配置 | `ENCRYPTION.md` |
|
||||
| ETCD 加密验证 | `VERIFICATION.md` 第9部分 |
|
||||
| 安全最佳实践 | `DEPLOYMENT.md` 安全最佳实践部分 |
|
||||
| 灾难恢复 | `DEPLOYMENT.md` 灾难恢复部分 |
|
||||
|
||||
## 生产就绪检查清单
|
||||
|
||||
- [ ] 所有 Pods 都在 Running 状态
|
||||
- [ ] JWT Secret 已创建并正确挂载
|
||||
- [ ] RBAC 权限验证通过
|
||||
- [ ] Redis Cluster 健康(3/3 节点)
|
||||
- [ ] ETCD 加密已启用(如需要)
|
||||
- [ ] 监控和日志聚合正常工作
|
||||
- [ ] 密钥轮换计划已制定
|
||||
- [ ] 备份和恢复流程已文档化
|
||||
- [ ] 安全审计日志已启用
|
||||
- [ ] 端到端测试已通过
|
||||
|
||||
## 下一步行动
|
||||
|
||||
### 短期(本周)
|
||||
|
||||
1. **部署 Secret 和 RBAC**
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/secrets/jwt-secret.yaml
|
||||
```
|
||||
|
||||
2. **更新 Deployments**
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/service/user/user-rpc.yaml
|
||||
kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
||||
```
|
||||
|
||||
3. **验证部署**
|
||||
```bash
|
||||
./verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
### 中期(本月)
|
||||
|
||||
1. **实现 JWT 集成**
|
||||
- 创建 gRPC 拦截器
|
||||
- 实现登录/登出端点
|
||||
- 添加 JWT 中间件到 REST API
|
||||
|
||||
2. **端到端测试**
|
||||
- 测试令牌生成和验证
|
||||
- 测试令牌刷新
|
||||
- 测试 CSRF 防护
|
||||
|
||||
### 长期(本季度)
|
||||
|
||||
1. **启用 ETCD 加密**
|
||||
- 按照 `ENCRYPTION.md` 配置
|
||||
- 验证所有 Secrets 都已加密
|
||||
|
||||
2. **生产部署**
|
||||
- 启用审计日志
|
||||
- 配置监控和告警
|
||||
- 制定密钥轮换政策
|
||||
|
||||
## 支持
|
||||
|
||||
如遇到问题:
|
||||
|
||||
1. **检查日志**
|
||||
```bash
|
||||
kubectl logs -n juwan -l app=user-rpc -f
|
||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
||||
```
|
||||
|
||||
2. **运行验证脚本**
|
||||
```bash
|
||||
chmod +x deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
./deploy/k8s/secrets/verify-jwt-setup.sh
|
||||
```
|
||||
|
||||
3. **查看详细文档**
|
||||
- 部署问题 → `DEPLOYMENT.md`
|
||||
- 代码集成 → `INTEGRATION.md`
|
||||
- ETCD 加密 → `ENCRYPTION.md`
|
||||
- 诊断 → `VERIFICATION.md`
|
||||
|
||||
## 总结
|
||||
|
||||
这个系统为微服务提供了:
|
||||
|
||||
✅ **安全的身份验证** - JWT 令牌 + HS256 签名
|
||||
✅ **灵活的令牌管理** - 7天有效期,30天可刷新
|
||||
✅ **高可用性** - Redis Cluster 自动故障转移
|
||||
✅ **权限隔离** - RBAC 限制密钥访问
|
||||
✅ **数据加密** - ETCD 加密保护敏感信息
|
||||
✅ **请求保护** - Envoy CSRF 双令牌验证
|
||||
|
||||
现在可以部署并集成到应用中了!
|
||||
|
||||
+507
-507
File diff suppressed because it is too large
Load Diff
@@ -1,60 +1,60 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: jwt-secret
|
||||
namespace: juwan
|
||||
type: Opaque
|
||||
data:
|
||||
# base64 encoded: your-secret-jwt-key-change-this-in-production
|
||||
secret-key: eW91ci1zZWNyZXQtand0LWtleS1jaGFuZ2UtdGhpcy1pbi1wcm9kdWN0aW9u
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: jwt-secret-reader
|
||||
namespace: juwan
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["jwt-secret"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: user-rpc-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: envoy-gateway-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: jwt-secret
|
||||
namespace: juwan
|
||||
type: Opaque
|
||||
data:
|
||||
# base64 encoded: your-secret-jwt-key-change-this-in-production
|
||||
secret-key: eW91ci1zZWNyZXQtand0LWtleS1jaGFuZ2UtdGhpcy1pbi1wcm9kdWN0aW9u
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: jwt-secret-reader
|
||||
namespace: juwan
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["jwt-secret"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: user-rpc-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: user-rpc
|
||||
namespace: juwan
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: envoy-gateway-jwt-secret-reader
|
||||
namespace: juwan
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: jwt-secret-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: envoy-gateway
|
||||
namespace: juwan
|
||||
|
||||
Generated
+1153
-1153
File diff suppressed because it is too large
Load Diff
+16
-16
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "npm@11.9.0+sha512.04166853ddba142ca98f86fb57b1258a7c6c59ccb82acb3cf141b77a315898acaaed47395e74f7e0c7b69c486008e68be6a6381ef1aee5a23dd82e0e61decd68",
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^8.2.0",
|
||||
"@inquirer/search": "^4.1.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"glob": "^13.0.1",
|
||||
"hereby": "^1.11.1",
|
||||
"postgres": "^3.4.8",
|
||||
"prompts": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"execa": "^9.6.1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"type": "module",
|
||||
"packageManager": "npm@11.9.0+sha512.04166853ddba142ca98f86fb57b1258a7c6c59ccb82acb3cf141b77a315898acaaed47395e74f7e0c7b69c486008e68be6a6381ef1aee5a23dd82e0e61decd68",
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^8.2.0",
|
||||
"@inquirer/search": "^4.1.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"glob": "^13.0.1",
|
||||
"hereby": "^1.11.1",
|
||||
"postgres": "^3.4.8",
|
||||
"prompts": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"execa": "^9.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user