0%

nohup

1
nohup command params >/dev/null 2>&1 &

nohup 表示终端关闭时不挂断程序,否则会因为SIGHUB信号在关闭终端时自动结束程序

command params 是需要执行的命令和命令参数

>/dev/null 表示将标准输出重定向到空设备即丢弃输出,也可以改成重定向到指定的文件

2>&1 表示将stderr错误输出重定向到标准输出

最后的 &表 示在后台执行

jobs

查看当前终端放入后台的工作,可以使用 -l 参数增加显示PID

对于前台执行的程序,通过ctrl+z,可以挂起程序,再使用

1
bg [%job_num]

可以将程序放到后台继续执行,%job_num是任务编号,可以省略,省略时操作最后一个非后台运行的程序

也可以将程序放回前台执行,通过

1
fg [%job_num]

disown

在后台执行和是否会挂断是两件事。

我们前面通过 ctrl+z,bg 把任务放在后台运行,但是终端退出仍然会被自动结束,使用

1
disown -h [%job_num] 

可以将任务免于挂断,相当于补了一个nohup

-h 选项的意思是只标记不挂断,而不移出任务列表,通过jobs命令仍然可以看到,如果不加-h 则会移出任务列表同时标记不挂断。

screen

前面讲了如何在后台执行程序并免于挂断,但有时候我们还希望和程序交互,在后台执行就并不方便了,这时候我们可以通过screen在前台执行,同时免于挂断,一个screen就相当于一个可以随时断开随时可以恢复的终端窗口。

新建一个名为name的screen

1
screen -S name

重新连接到名为name的screen

1
screen -r name

附到一个连接状态名为name的screen

1
screen -x name

查看当前的screen列表

1
screen -ls

断开连接当前screen

1
ctrl+a d

reptyr

从一个终端(或后台)接收运行的进程,并将其迁移到另一个终端(的前台)。

1
reptyr -T <PID>

这个命令我就没成功执行过。不知为何

移动光标

gg 跳至文首
G 调至文尾
行号G 跳转到指定行
w 移动到后一个单词第一个字符
b 移动到前一个词的第一个字符

up、down、left、right、PageDown、PageUp、Home、End 都是可用的,没必要用vi重定义的按键

删除

dd 删除光标所在行
d+移动光标 从当前光标位置删除到光标移动后的位置
例如删除全文时,可以使用 gg dG

查找/替换

/ pattern 从光标处向后查找
? pattern 从光标处向前查找
n 查找下一个
. 执行前次的命令操作,结合/和n可以实现逐一确认替换
:%s/src_str/dst_str/g 替换全部的src_str为dst_str
:‘<,’>s/src_str/dst_str/g 替换选中区域的src_str为dst_str,在可视模式选中后直接按:会自动出现:‘<,’>
u Undo
Ctrl+r Redo
p 粘贴到光标的后面或当前行的下面(取决于复制时是逐字模式还是逐行模式)
P 粘贴到光标的前面或当前行的上面(取决于复制时是逐字模式还是逐行模式)

可视模式(选择/剪切/复制)
v 进入逐字可视模式
V 进入逐行可视模式
Ctrl+v 进入块(列)选择模式
移动光标 从当前光标位置选择到光标移动后的位置
选中后的操作
U 将选中的目标全部替换为大写
u 将选中的目标全部替换为小写
c 剪切
y 复制
d 删除

输入模式

i 从当前光标处进入输入模式
o 在当前行之下新加一行,并进入输入模式
ESC 退出插入模式

编辑模式

:q 退出
:wq 保存并退出
:q! 退出放弃保存
:wq! 强制保存并退出
:wq! sudo tee % 提权并保存退出
:set number 显示行号
:set ruler 显示光标位置在右下角

Nginx (engine x) 是一个高性能的HTTP服务器和反向代理服务器。

默认可执行文件路径

1
/usr/sbin/nginx

默认配置文件路径

1
/etc/nginx/nginx.conf

nginx.conf主要内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
http {
...
##
# Logging Settings
##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
...
##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
...

可以看到默认的访问日志路径和错误日志路径:

1
/var/log/nginx/access.log
1
/var/log/nginx/error.log

当访问出错时可以通过这两个日志来分析原因。

nginx配置文件支持include的方式实现多配置文件管理,这样我们可以把不同站点或不同服务配置在单独的文件中。

/etc/nginx/conf.d/ 目录下的所有 .conf文件和 /etc/nginx/sites-enabled/ 目录下的所有文件内容都会被包含作为配置。

/etc/nginx/conf.d/ 默认是空的,/etc/nginx/sites-enabled/ 下有一个default文件软链接指向/etc/nginx/sites-available/default

我们也可以采取这种方式,在/etc/nginx/sites-available/目录下创建不同的需要被代理服务配置,然后通过软链接控制是否启用。

来看一下

1
/etc/nginx/sites-enabled/default

文件的主要内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;
...
root /var/www/html;

# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;

server_name _;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
...
}

# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

我们看到这里有两个server{}块,一个server{}块就是一个服务器配置,上面部分是一个默认服务器的配置,在安装nginx之后访问 http://localhost 可以看到的默认页面就是通过这里的配置,下面部分(被注释掉的)是一个简单的web服务器配置样例。

listen 80 default_server; 是监听所有IPv4的80端口,也可以绑定IP地址 listen 127.0.0.1:80 这样服务器就只绑定了localhost的80端口,只能通过本地访问web服务,default_server用于表示这个server是默认server,当配置的多个server都绑定到了80端口,首先会通过匹配server_name来转发请求,如果没有找到匹配的则会使用 default_server ,如果没有配置 default_server ,则会使用 server_name _; 如果有找到 server_name _; 则会使用配置文件排序第一的第一个server块。

listen [::]:80 是配置监听所有的IPv6的80端口。

root /var/www/html; 是配置网站文件的根路径,多个站点可以在/var/www/下创建不同的目录作为根,如果作为反向代理服务器,可以没有root配置。

index index.html 是配置网站默认访问的文件名,当我们的URL没有以文件名结尾时会自动尝试访问配置的默认文件名,例如我们访问http://www.baidu.com 其实是访问到了 http://www.baidu.com/index.html 也就是网站根下的index.html 文件,如果作为反向代理服务器,可以没有index配置。

location / {}块,/ 用于匹配请求的 URI,如果匹配成功就用 {}块中的配置去处理。

比如 http://kyo86.com/archives http是协议,kyo86.com是域名,/archives是URI。

location 匹配的方式有多种:

  • 精准匹配
  • 正则匹配
  • 前缀匹配

其语法如下

1
location [ = | ~ | ~* | ^~ ] uri {...}

其中各个符号的含义:

  • =:精准匹配
  • ~:区分大小写的正则匹配;
  • ~*:不区分大小写的正则匹配;
  • ^~:匹配 URI 的前缀;

默认就是前缀匹配,但是和^~的优先级不同,当一个地址可以匹配多条location配置时,按如下顺序匹配:

=,^~ 匹配最长的,~ 匹配最前的,~* 匹配最前的,无修饰符的

所以location / {} 相当于是一个保底匹配,优先级最低的通用匹配。

用例:

匹配前后端分离时对后端api的请求,这里的用法就是反向代理服务器

1
2
3
4
5
6
7
8
9
location ^~/api/ {
proxy_pass http://localhost:8082/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;

rewrite ^/api/(.*)$ /$1 break;
}

匹配特定扩展名的请求

1
location ~* \.(gif|jpg|jpeg)$ {...}

try_files $uri $uri/ =404; 定义了尝试访问文件的方式,还是拿 http://kyo86.com/archives 举例,$uri的意思是拿/archives去root路径下查找文件,如果没有找到尝试$uri/ 它的意思是去root目录下的/archives/ 目录下,再根据index配置的文件去逐一查找,如果还是没有找到,=404的意思是跳转到404错误页,HTTP状态码404表示找不到文件或者资源。

检查配置文件正确性

1
nginx -t

以不中断服务的方式重新加载配置文件

1
nginx -s reload

记录一些实际使用中服务器的配置示例

代理ngrok服务器配置(这里server_name可以配置多个,而且其中一个是带*的,称为泛域名)

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 172.19.64.114:80 default_server;
server_name *.ngrok.kyo86.com ngrok.kyo86.com;
location / {
proxy_pass http://127.0.0.1;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120;
}
}

代理jupyter服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 172.19.64.114:80;
server_name jupyter.kyo86.com;
location / {
proxy_pass http://localhost:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

hexo博客服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 172.19.64.114:80;
server_name kyo86.com www.kyo86.com;
root /var/www/hexo;

index index.html index.htm;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
}

这是另一台服务器上配置了一个前后端分离的公司内部使用的WEB管理后台,后端服务器在30101端口。

前端用了htpasswd配置了基本的密码认证(搜索htpasswd可以得到配置用户名密码的方法)

后端用allow,deny控制允许访问的IP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm;

server_name _;

location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;

try_files $uri $uri/ =404;
}
location ^~/api/ {
allow 127.0.0.1/32;
allow 192.168.0.0/16;
deny all;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;

rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://localhost:30101/;
}
}

上一篇我们讲到 通过ngrok实现内网穿透,在运行服务器时指定了参数 -httpAddr=:30080 -httpsAddr=:30443

1
./bin/ngrokd -domain=ngrok.kyo86.com -tlsKey=./keys/device.key -tlsCrt=./keys/device.crt -httpAddr=:30080 -httpsAddr=:30443

是为了避免和nginx服务器端口占用冲突,但这会导致我们通过子域名来映射web服务时只能通过30080端口访问,而不是通常的80端口。

例如我们启动一个hexo博客

1
hexo s
1
2
3
4
daimingzhuang@ecs-windows:/mnt/d/Projects/blog$ hexo s
INFO Validating config
INFO Start processing
INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.

然后将其使用的4000端口通过hexo子域名映射出去

1
./ngrok.exe -config="ngrok.cfg" -subdomain=hexo -proto=http 4000
1
2
3
4
5
6
ngrok                                                                (Ctrl+C to quit)                       Tunnel Status                 online
Version 1.7/1.7
Forwarding http://hexo.ngrok.kyo86.com:30080 -> 127.0.0.1:4000
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms

这时候任何人都可以通过 http://hexo.ngrok.kyo86.com:30080 来访问博客了但这显然不是很令人舒服,我希望能通过 http://hexo.ngrok.kyo86.com 来访问。

通常80端口会留给nginx使用,在nginx上配置反向代理,根据域名或者根据路径把请求转发到其他端口或者其他内部服务器上。

下面我们就通过nginx配置将 *.ngrok.kyo86.com 的请求转发到 30080端口

1
vim /etc/nginx/sites-enabled/default

新建一个server configuration如下

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name *.ngrok.kyo86.com;
location / {
proxy_pass http://127.0.0.1:30080;
proxy_redirect off;
proxy_set_header Host $http_host:30080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120;
}
}

server_name来指定域名,特别注意 $http_host:30080 这里一定要指定端口,不然当我们使用 http://hexo.ngrok.kyo86.com 访问时请求确实会被转发到ngrok服务器,但是ngrok会根据 http://hexo.ngrok.kyo86.com 这个地址去寻找tunnel,而被注册的地址是 http://hexo.ngrok.kyo86.com:30080 所以会找不到tunnel,也就无法将请求转发给hexo服务器。

检测nginx配置文件合法性

1
nginx -t

重启nginx服务器

1
service nginx restart

现在就可以通过 http://hexo.ngrok.kyo86.com 来访问hexo博客了,当然仍然可以通过 http://hexo.ngrok.kyo86.com:30080 来访问。

到现在只剩下一点稍微不爽,就是在运行ngrok客户端时,其中回显

1
Forwarding                    http://hexo.ngrok.kyo86.com:30080 -> 127.0.0.1:4000

仍然带着:30080。

实际上我们可以把nginx和ngrok绑定在不同IP的80端口上,nginx绑定在公网IP:80,ngrok绑定在localhost:80,这样是没有冲突的。

修改nginx配置

1
vim /etc/nginx/sites-enabled/default
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 172.19.XX.XXX:80;
server_name *.ngrok.kyo86.com;
location / {
proxy_pass http://127.0.0.1;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 120;
}
}

这里的 listen 在端口前面加了IP地址,我在阿里云上,虽然ECS是有公网地址的,但查看ifconfig时没有看到公网地址,我就指定了连接到公网的那块网卡的地址,这时候 $http_host就不要带上:80了,因为这时向ngrok注册tunnel的地址是不带端口的。

还要注意,其他的server配置如果是使用80端口,都要改成指定IP地址绑定,不然后面会和ngrok冲突。

重启nginx服务器

1
service nginx restart

运行ngrok服务器

1
./bin/ngrokd -domain=ngrok.kyo86.com -tlsKey=./keys/device.key -tlsCrt=./keys/device.crt -httpAddr=127.0.0.1:80 -httpsAddr=127.0.0.1:443

这里把端口绑定成了127.0.0.1的80和443。

启动ngrok客户端,回显变成了

1
Forwarding                    http://hexo.ngrok.kyo86.com -> 127.0.0.1:4000

测试访问 http://hexo.ngrok.kyo86.com ,成功,完美。

如果启动ngrokd时报错

1
panic: listen tcp 127.0.0.1:80: bind: address already in use

去检查nginx配置看是不是所有80端口前都加了IP地址,或者用

1
netstat -anp|grep :80

来查看绑定了80端口的进程。

ngrok和ssh-tunnel做的事有点像,ngrok专注于将一个本地服务器暴露到公网。

它的工作原理是首先有一个公网的ngrok服务器,然后在内网需要暴露的机器上通过ngrok客户端建立tunnel来暴露指定端口。

国内有一些免费的ngrok服务器,也可以自己搭建,这里我自己搭建。

搭建ngrok服务器

参考:https://zhuanlan.zhihu.com/p/149968878

下载源码

1
2
git clone https://github.com/inconshreveable/ngrok.git
cd ngrok

生成证书

1
2
3
4
5
6
7
cd keys
export NGROK_DOMAIN="ngrok.kyo86.com"
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

替换证书

1
2
3
4
cp rootCA.pem ../assets/client/tls/ngrokroot.crt
cp device.crt ../assets/server/tls/snakeoil.crt
cp device.key ../assets/server/tls/snakeoil.key
cd ..

编译linux服务端

1
2
set GOOS=linux GOARCH=amd64
make release-server

编译linux客户端

1
2
set GOOS=linux GOARCH=amd64
make release-client

注意:因为客户端和服务器在编译时都使用了证书,所以他们是一一对应的,就是说ngrok客户端并不是通用的,不能用来连接其他的ngrok服务器。

启动服务器端

1
./bin/ngrokd -domain=ngrok.kyo86.com -tlsKey=./keys/device.key -tlsCrt=./keys/device.crt -httpAddr=:30080 -httpsAddr=:30443

服务器端默认使用4443端口,所以需确保防火墙允许4443端口访问

在客户端建立端口映射

创建配置文件ngrok.cfg

1
2
server_addr: "ngrok.kyo86.com:4443"
trust_host_root_certs: false

暴露服务器有两种方式:

一是在运行服务器时指定的http和https端口通过子域名的方式来暴露web服务

二是通过随机端口来暴露指定的tcp端口

暴露WEB服务

把本地的8080(http)端口映射到通过 http://jupyter.ngrok.kyo86.com:30080 访问

1
./ngrok -config=ngrok.cfg -subdomain=jupyter -proto=http 8080

访问子域名需要指定泛域名解析,即 *.ngrok.kyo86.com 的解析。

如果不指定-proto默认就是http+https

暴露TCP端口

把本地的22(tcp)端口映射到 ngrok.kyo86.com上的一个随机端口

1
./ngrok -config=ngrok.cfg -proto=tcp 22

通过 wsl export/import 命令可以实现WSL的备份与还原,在还原时可以指定安装路径从而实现安装路径迁移。

备份
1
2
3
4
5
6
7
8
# 查看已安装的所有子系统
wsl --list --all
# 查看正在运行的子系统
wsl --list --running
# 若要备份的子系统正在运行,则将其终止
wsl -t Ubuntu-18.04
# 导出子系统到.tar文件 wsl --export <DistributionName> <FileName>
wsl --export Ubuntu-18.04 D:\wsl\backup\ubuntu1804.tar
还原
1
2
3
4
# 注销子系统(视情况,看是否需要)
wsl --unregister Ubuntu-18.04
# 导入子系统 wsl --import <DistributionName> <InstallLocation> <FileName>
wsl --import Ubuntu-18.04 D:\wsl\Ubuntu1804\ D:\wsl\backup\ubuntu1804.tar

新还原的Ubuntu子系统的默认用户会变成root(无论是在Windows Terminal中打开Ubuntu还是在vscode中通过Remote-WSL启动Ubuntu)
可以通过下面的命令

修改子系统默认用户
1
2
3
4
5
6
# 定义函数WSL-SetDefaultUser,接收参数distro和user
Function WSL-SetDefaultUser ($distro, $user) { Get-ItemProperty Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\*\ DistributionName | Where-Object -Property DistributionName -eq $distro | Set-ItemProperty -Name DefaultUid -Value ((wsl -d $distro -u $user -e id -u) | Out-String); };
# 调用函数,WSL-SetDefaultUser <DistroName> <UserName>
WSL-SetDefaultUser Ubuntu-18.04 daimingzhuang
# 取消函数定义
Remove-Item Function:WSL-SetDefaultUser

参考:

WSL备份与还原 | 小肥羊吃草不吃肉 (xfy-learning.com)

启用“适用于 Linux 的 Windows 子系统”

1
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

启用“虚拟机平台”可选组件

1
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

将 WSL 版本设置为 1 或 2

1
wsl --set-default-version 2

列出已安装的 Linux 发行版

1
wsl --list --online

安装特定的 Linux 发行版

1
wsl --install -d Ubuntu-18.04

从官网安装比较慢,也可以下载安装包解压后通过ubuntu1804.exe安装
https://pan.baidu.com/s/1Rf9vTiXoUkpWWMQVlICYcw?pwd=7qib

需要注意的是解压路径就是WSL的安装路径,安装好不能移除,否则WSL就损坏了

BTW:

输入wsl bash找不到路径的问题

启用“适用于 Linux 的 Windows 子系统”后找不到,可以先重启再试,和系统版本有关,我遇到过需要重启的系统,也遇到过不需要重启的系统。

之前可以使用,某种操作后突然不能找不到bash,可能是wsl被损坏,我遇到一次是在ECS上安装Docker Desktop,不能用后将Docker Desktop删除,wsl就损坏了

解决方案:

wsl.exe --list --all (列出所有的linux系统)

wsl.exe --unregister (上面所列出的名字)

重新安装

hexo中插入图片通常使用相对网站根的路径,例如将图片放在source/images下,那么插入图片的src如下

1
<img src="/images/saber.jpg"/>

而这种方式在typora中编辑时无法正常查看

其实可以在 md文件头部的配置项中,添加 typora-root-url: 来指定根目录

hexo source的结构是

1
2
3
4
source
├─about
├─images
└─_posts

我们的博文.md文件在_posts目录下,所以我们可以设置typora-root-url: ../,把根设成source目录

例如这篇blog的md头部是

1
2
3
4
5
title: 兼容hexo和typora的图片插入方式
date: 2022-04-15 17:46:22
typora-root-url: ../
tags:
- Hexo

插入下面图片的代码是

1
<img src="/images/saber.jpg" style="zoom: 25%;" />

可以在 scaffolds/post.md 中加入 typora-root-url: ../ 以便执行hexo new命令时自动生成

想从家里访问公司局域网内的服务器,最直接的方式就是搭建VPN,我尝试过Ubuntu下使用PPTPD搭建VPN,实际使用下来并不稳定,经常断线,后来有类似需求的时候我都是通过SSH Tunnel完成的。
先说一下环境,公司服务器在局域网192.168.1.0/24网段,公司网关路由器192.168.1.1上通过端口映射暴露了192.168.1.99的22端口:

<公司公网IP>:20742 ->192.168.1.99:22

我要从家里访问公司局域网内的 192.168.1.99:5432 和 192.168.1.166:6379
我们可以通过

1
2
autossh -M 0 -CNg -L 5432:192.168.1.99:5432 root@<公司公网IP> -p 20742
autossh -M 0 -CNg -L 6379:192.168.1.166:6379 root@<公司公网IP> -p 20742

可以先开通本地对跳板机192.168.1.99的密钥登录,然后增加-f参数直接在后台运行,详请搜索SSH密钥登录,使用密码登录时不能加-f

这样就建立了端口转发

localhost:5432 -> (公司局域网)192.168.1.99:5432
localhost:6379 -> (公司局域网)192.168.1.166:6379

我有一些程序和脚本里写好了访问的服务器地址就是192.168.1.99 和192.168.1.166,我不想修改成localhost或者127.0.0.1,
我们可以通过

1
2
ifconfig enp1s0:0 192.168.1.99 netmask 255.255.255.255 up
ifconfig enp1s0:1 192.168.1.166 netmask 255.255.255.255 up

创建虚拟网卡并把IP设置为192.168.1.99和192.168.1.166,这样这两个IP都指向了本地。
至此需求就解决了。

但是更进一步,如果我想访问的两个服务器上的相同端口,例如 192.168.1.99:6379 和 192.168.1.166:6379 怎么办呢?
我想到了做两个容器分别去建立SSH Tunnel,并把两个容器的IP设置分别设置为 192.168.1.99和192.168.1.166
首先我们创建一个docker network在 192.168.1.0/24网段

1
docker network create --gateway=192.168.1.1 --subnet=192.168.1.0/24 company

创建一个ubuntu容器加入company网络并指定IP地址为192.168.1.99,同时暴露6379端口

1
docker run -dit --name proxy99 -h proxy99 --net company --ip 192.168.1.99 --expose 6379 daimingzhuang/ubuntu:18.04 bash

连上去

1
docker exec -it proxy99 bash

和前面一样建立SSH Tunnel

1
autossh -M 0 -CNg -L 6379:192.168.1.99:6379 root@<公司公网IP> -p 20742

在宿主机上通过redis-cli连接192.168.1.99:6379

1
redis-cli -h 192.168.1.99

成功
192.168.1.166:6379 也同样操作
总结起来就是用docker创建跳板机容器,用ssh tunnel做端口映射。

我做了一个docker镜像(daimingzhuang/ssh_proxy)来方便进行上面的操作
Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM ubuntu:18.04
MAINTAINER DaiMingzhuang "kyo86.dai@gmail.com"
ENV REFRESHED_AT=2022-04-07
RUN echo deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse>/etc/apt/sources.list\
&& echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse>>/etc/apt/sources.list\
&& echo deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse>>/etc/apt/sources.list
RUN apt update -yqq \
&& apt install -y vim-tiny autossh expect \
&& rm -rf /var/lib/apt/lists/* \
&& ln -s /usr/bin/vim.tiny /usr/bin/vim

EXPOSE 22 80 8080 1433 3306 6379 9092 5432 27017

ADD setup.sh /root/setup.sh
WORKDIR "/root/"
CMD ["bash"]

/root/setup.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

user='root'
passwd='跳板机密码'
LAN_IP='跳板机局域网IP'
WAN_IP='网关的公网IP'
WAN_PORT=跳板机通过端口映射暴露在公网的SSH端口
ports=(22 80 8080 1433 3306 6379 9092 5432 27017)

for((i=0;i<${#ports[@]};i++)); do

/usr/bin/expect <<-EOF
set time 10
spawn autossh -M 0 -CNg -L ${ports[$i]}:$LAN_IP:${ports[$i]} $user@$WAN_IP -p $WAN_PORT
expect {
"*(yes/no)?" { send "yes\n"; exp_continue }
"*password:" { send "$passwd\n" }
}

expect eof
EOF

done

准备工作

创建binlog2sql账号

1
CREATE USER binlog2sql@'%' IDENTIFIED BY 'binlog2sql';

授权

1
2
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO binlog2sql@'%' identified by 'binlog2sql';
FLUSH privileges;

服务器配置

默认配置文件路径

1
/etc/mysql/mysql.conf.d/mysqld.cnf

查找server-id,附近的选项按如下设置

1
2
3
4
5
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log # 开启binlog,设置路径
max_binlog_size = 1G # 设置单个binlog大小上限
binlog_format = row # 设置binlog模式,binlog2sql要求必须是row模式
binlog_row_image = full # 默认的,可以不设

重启mysql

1
service mysql restart

下载binlog2sql

1
git clone https://github.com/danfengcao/binlog2sql.git /opt/binlog2sql

安装依赖

1
pip install -r /opt/binlog2sql/requirements.txt

创建binlog2sql别名

为了方便使用binlog2sql.py脚本,可以把别名设置写在~/.bash_aliases文件中

1
alias binlog2sql='python /opt/binlog2sql/binlog2sql/binlog2sql.py -h 127.0.0.1 -u binlog2sql -p binlog2sql'

如何查看一个binlog文件记录的position的范围?

1
mysqlbinlog -v mysql-bin.000010|tail -n 100|less

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
……
# at 4356297
#220402 14:12:19 server id 1 end_log_pos 4356362 CRC32 0xeb816e97 Anonymous_GTID last_committed=5723 sequence_number=5
724 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 4356362
#220402 14:12:19 server id 1 end_log_pos 4356439 CRC32 0x91cd3acc Query thread_id=45 exec_time=0 error_code=0
SET TIMESTAMP=1648879939/*!*/;
BEGIN
/*!*/;
# at 4356439
#220402 14:12:19 server id 1 end_log_pos 4356548 CRC32 0x6635890f Table_map: `gogoal_v2`.`rpt_target_price_adjust` mapped t
o number 226
# at 4356548
#220402 14:12:19 server id 1 end_log_pos 4356766 CRC32 0xa3f8f451 Write_rows: table id 226 flags: STMT_END_F

BINLOG '
Q+lHYhMBAAAAbQAAAMR5QgAAAOIAAAAAAAEACWdvZ29hbF92MgAXcnB0X3RhcmdldF9wcmljZV9h
ZGp1c3QAFAMDDw8PAwMPDwoKBQUFAxISCBISETwAlgDCAZYAWAIICAgAAAAA/v8PD4k1Zg==
Q+lHYh4BAAAA2gAAAJ56QgAAAOIAAAAAAAEAAgAU////AAD8HekUABzdFQAGMDAwMDAyB+S4h+en
kUE3AOS4h+enkUHvvJrliKnmtqbnjofljovlipvph4rmlL7vvIzmlrDkuJrliqHlhZHnjrDlop7p
lb8WAAAAPQAAAAzlub/lj5Hor4HliLgXAOmDremVhyzkuZDliqDmoIss6YKi6I6YgcwPS8wPKVyP
wvVoPEAzMzMzM3M5QGZmZmZmJjNAAgAAAJmshMqAmayEyoBC83pdAQAAAFH0+KM=
'/*!*/;
### INSERT INTO `gogoal_v2`.`rpt_target_price_adjust`
### SET
### @1=1370397
### @2=1432860
### @3='000002'
### @4='万科A'
### @5='万科A:利润率压力释放,新业务兑现增长'
### @6=22
### @7=61
### @8='广发证券'
### @9='郭镇,乐加栋,邢莘'
### @10='2022:04:01'
### @11='2022:02:11'
### @12=28.410000000000000142
### @13=25.449999999999999289
### @14=19.149999999999998579
### @15=2
### @16='2022-04-02 12:42:00'
### @17='2022-04-02 12:42:00'
### @18=5863306050
### @19=NULL
### @20=NULL
# at 4356766
#220402 14:12:19 server id 1 end_log_pos 4356797 CRC32 0x5d69576f Xid = 715129
COMMIT/*!*/;
# at 4356797
#220402 14:12:19 server id 1 end_log_pos 4356862 CRC32 0x941d7ebe Anonymous_GTID last_committed=5724 sequence_number=5725 rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 4356862
#220402 14:12:19 server id 1 end_log_pos 4356939 CRC32 0x25f3bc96 Query thread_id=45 exec_time=0 error_code=0
SET TIMESTAMP=1648879939/*!*/;
BEGIN
/*!*/;
# at 4356939
#220402 14:12:19 server id 1 end_log_pos 4357048 CRC32 0x94e54d42 Table_map: `gogoal_v2`.`rpt_target_price_adjust` mapped to number 226
# at 4357048
#220402 14:12:19 server id 1 end_log_pos 4357304 CRC32 0x6be0ae0b Write_rows: table id 226 flags: STMT_END_F
……

其中的 #at 后面的数字就是position,文件首个和最后一个position就是这个binlog记录的position的范围。

另外,举例来说,如果我们要解析出的insert语句,需要从4356362解析到4356766,从BEGIN上面的position到COMMIT上面的position,至于update或delete应该是类似的。

1
binlog2sql -d gogoal_v2 --start-file=mysql-bin.000010 --start-position=4356362 --stop-position=4356766

输出

1
INSERT INTO `gogoal_v2`.`rpt_target_price_adjust`(`id`, `report_id`, `stock_code`, `stock_name`, `title`, `report_type`, `organ_id`, `organ_name`, `author`, `current_create_date`, `previous_create_date`, `current_target_price`, `previous_target_price`, `current_price`, `price_adjust_mark`, `entrytime`, `updatetime`, `tmstamp`, `UTSUPTIME`, `UTSFIRSTTIME`) VALUES (1370397, 1432860, '000002', '万科A', '万科A:利润率压力释放,新业务兑现增长', 22, 61, '广发证券', '郭镇,乐加栋,邢莘', '2022-04-01', '2022-02-11', 28.41, 25.45, 19.15, 2, '2022-04-02 12:42:00', '2022-04-02 12:42:00', 5863306050, NULL, NULL); #start 4356439 end 4356766 time 2022-04-02 14:12:19

如何保留一个数据库的全量备份?

有两种形式,一种是通过mysqldump把数据库导出成sql,正常应该通过这种形式

1
mysqldump --master-data=2 gogoal_v2> /data/dbbackup/20220406/gogoal_v2.sql

这里的–master-data=2选项会在生成的sql中生成被注释掉的 CHANGE MASTER 命令,里面有binlog文件编号和位置编号

另一种是直接备份数据库目录,我们的mysql中只有一个数据库gogoal_v2,所以备份目录是最方便的。
那么我们就要手动确定备份下来截止的binlog编号和位置编号,为了防止一边备份数据库一边在变,我们需要先停止数据库,而恰好我们操作的这个数据库又不是生产环境使用的,可以随时停止

首先停止数据库

1
service mysql stop

查看/data/mysql-binlog下最新的binlog文件和最新的position并记录下来,可以写在后面的备份目录下

1
mysql-bin.000012 5055197

备份/var/lib/mysql下的全部文件

1
cp -rp /var/lib/mysql /data/dbbackup/20220406/

最后在恢复启动数据库

1
service mysql start

如何还原一个全量备份?

我们将备份还原到一个新的mysql容器中

首先创建一个mysql容器

1
docker run --name mysql -Pdit daimingzhuang/mysql

daimingzhuang/mysql和官方mysql容器的区别是按原mysql的配置做了字符编码、大小写相关的设置,允许root远程访问,设置了默认密码

1
2
3
4
5
lower_case_table_names=1
character_set_server=utf8mb4
collation-server=utf8mb4_unicode_ci
init_connect='SET NAMES utf8mb4'
skip-character-set-client-handshake=true

实际使用的命令还包含了资源使用限制

1
2
3
4
5
6
7
8
9
docker run --name mysql -Pdit \
-h mysql \
--cpuset-cpus="1,3" \
--memory=4G \
--device-read-bps /dev/sdd:80mb \
--device-write-bps /dev/sdd:50mb \
--device-read-bps /dev/sdb:80mb \
--device-write-bps /dev/sdb:50mb \
daimingzhuang/mysql

然后连到容器停止mysql服务

1
2
docker exec -it mysql bash
service mysql stop

把备份文件复制到容器中

1
docker cp /data/dbbackup/20220406/mysql mysql:/var/lib/

还需要复制debian.cnf解决服务控制权限问题(里面有debian-sys-maint用户的密码)

1
docker cp /etc/mysql/debian.cnf mysql:/etc/mysql/debian.cnf

再到容器中修改目录权限并启动mysql

1
2
chown -R mysql:mysql /var/lib/mysql
service mysql start

如何用binlog还原增量备份?

下面将在前面已经还原了全量备份的基础上,继续利用binlog还原到指定时间点
我们先看一下当前binlog文件

1
ll /var/log/mysql

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
total 1221304
drwx------ 2 mysql mysql 1024 Apr 7 06:25 ./
drwxr-xr-x 8 root root 1024 Apr 6 20:14 ../
-rw-r----- 1 mysql mysql 164683302 Mar 26 06:25 mysql-bin.000001
-rw-r----- 1 mysql mysql 1146243 Mar 27 06:25 mysql-bin.000002
-rw-r----- 1 mysql mysql 5487816 Mar 28 06:25 mysql-bin.000003
-rw-r----- 1 mysql mysql 176003823 Mar 29 06:25 mysql-bin.000004
-rw-r----- 1 mysql mysql 180791383 Mar 30 06:25 mysql-bin.000005
-rw-r----- 1 mysql mysql 189424937 Mar 31 06:25 mysql-bin.000006
-rw-r----- 1 mysql mysql 177521662 Apr 1 06:25 mysql-bin.000007
-rw-r----- 1 mysql mysql 1704056 Apr 1 10:43 mysql-bin.000008
-rw-r----- 1 mysql mysql 172659973 Apr 2 06:25 mysql-bin.000009
-rw-r----- 1 mysql mysql 4357335 Apr 2 18:28 mysql-bin.000010
-rw-r----- 1 mysql mysql 1212646 Apr 2 22:33 mysql-bin.000011
-rw-r----- 1 mysql mysql 5055220 Apr 6 17:01 mysql-bin.000012
-rw-r----- 1 mysql mysql 166213563 Apr 7 06:25 mysql-bin.000013
-rw-r----- 1 mysql mysql 4326594 Apr 7 14:12 mysql-bin.000014
-rw-r----- 1 mysql mysql 504 Apr 7 06:25 mysql-bin.index

我们可以估计需要还原到的数据点在哪个binlog文件中,比如我想还原到20220407 0点,那就应该在这里 Apr 7 06:25 mysql-bin.000013 ,事实上我们把000014或更多的binlog包含进来不要紧,只是浪费了执行命令的过滤时间

在原mysql上从binlog解析出全量备份后的增量sql

1
binlog2sql -d gogoal_v2 --start-file=mysql-bin.000013 --stop-file=mysql-bin.000014 --stop-datetime='2022-04-07 00:00:00' > inc_20220407000000.sql

–start-file和–start-position根据全量备份时记录的位置设置,我们全量备份时候记录的是mysql-bin.000012 5055197 ,而事实上因为我们在做全量备份的时候停止了mysql服务,重启后会写入新的binlog,所以我们可以从mysql-bin.000013开始还原,–stop-datetime 指定了还原到的时间点

把增量sql复制到容器内

1
docker cp ./inc_20220407000000.sql mysql:/root/

在容器内执行还原

1
2
docker exec -it mysql bash
mysql gogoal_v2 < /root/inc_20220407000000.sql

如何在没有全量备份的情况下将数据库退回到指定时间点?

当前时间 2022-04-12 13:38:00

假设我们想退回到 2022-04-11 15:00:00

在有全量备份的情况下我们可以通过上面的方法先还原全量备份再叠加通过binlog导出的增量sql来还原,但在没有全量备份或者全量备份比较久远的情况下,我们也可以通过binlog2sql导出基于当前数据的回退sql来还原。

首先停止数据库

1
service mysql stop

还是先看一下当前binlog文件

1
ll /var/log/mysql

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
total 1732017
drwx------ 2 mysql mysql 1024 Apr 12 13:39 ./
drwxr-xr-x 8 root root 1024 Apr 6 20:14 ../
-rw-r----- 1 mysql mysql 164683302 Mar 26 06:25 mysql-bin.000001
-rw-r----- 1 mysql mysql 1146243 Mar 27 06:25 mysql-bin.000002
-rw-r----- 1 mysql mysql 5487816 Mar 28 06:25 mysql-bin.000003
-rw-r----- 1 mysql mysql 176003823 Mar 29 06:25 mysql-bin.000004
-rw-r----- 1 mysql mysql 180791383 Mar 30 06:25 mysql-bin.000005
-rw-r----- 1 mysql mysql 189424937 Mar 31 06:25 mysql-bin.000006
-rw-r----- 1 mysql mysql 177521662 Apr 1 06:25 mysql-bin.000007
-rw-r----- 1 mysql mysql 1704056 Apr 1 10:43 mysql-bin.000008
-rw-r----- 1 mysql mysql 172659973 Apr 2 06:25 mysql-bin.000009
-rw-r----- 1 mysql mysql 4357335 Apr 2 18:28 mysql-bin.000010
-rw-r----- 1 mysql mysql 1212646 Apr 2 22:33 mysql-bin.000011
-rw-r----- 1 mysql mysql 5055220 Apr 6 17:01 mysql-bin.000012
-rw-r----- 1 mysql mysql 166213563 Apr 7 06:25 mysql-bin.000013
-rw-r----- 1 mysql mysql 173374732 Apr 8 06:25 mysql-bin.000014
-rw-r----- 1 mysql mysql 171723561 Apr 9 06:25 mysql-bin.000015
-rw-r----- 1 mysql mysql 471311 Apr 10 06:25 mysql-bin.000016
-rw-r----- 1 mysql mysql 3800429 Apr 11 06:25 mysql-bin.000017
-rw-r----- 1 mysql mysql 176073129 Apr 12 06:25 mysql-bin.000018
-rw-r----- 1 mysql mysql 1845781 Apr 12 12:41 mysql-bin.000019
-rw-r----- 1 mysql mysql 684 Apr 12 06:25 mysql-bin.index

我们可以估计需要还原到的时间点 2022-04-11 15:00:00 应该在 mysql-bin.000018 中,那么回退需要用到的binlog就是从000018到000019,导出回退sql的命令如下

1
binlog2sql --flashback -d gogoal_v2 --start-file=mysql-bin.000018 --stop-file=mysql-bin.000019 --start-date="2022-04-11 15:00:00" > flashback.sql

和之前命令的区别就是–flashback选项,表示导出符合指定条件内的回退sql语句。

之后创建一个新的mysql容器,把当前数据的/var/lib/mysql复制到容器中,修正权限。不要忘了重新启动前面停止的mysql服务。再把flashback.sql导入到容器数据库中即可实现回退。具体语句参照前面已经讲过的内容。