0%

启动一个容器

1
docker run -h 容器主机名 --name 容器名 -Pdit 镜像名

启动一个新的bash对运行中的容器进行管理

1
docker exec -it 容器名 bash

指定以root用户启动bash对容器进行管理

默认会按照容器启动的用户打开bash,如果想以root身份打开bash需要执行

1
docker exec -it -u root 容器名 bash

其他常用命令

镜像 容器
创建 build -t/commit create/run
删除 rmi rm
重命名 tag+rmi rename
查看列表 images ps/ps -a/ps -l -q
运行状态 stats top
查看日志 history --no-trunc logs
登录 login
拉取 pull
推送 push
查找 search
查看容器信息 inspect
发信号 kill
附到主进程 attach
执行新进程 exec

docker run 常用参数

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
-d   # 后台运行
-it # 打开STDIN,支持终端登录
--name <容器名>
-h <主机名>
-P # 随机映射所有配置在EXPOSE中的端口
-p [<宿主端口>:]<容器端口> # 指定映射一个端口,不指定宿主端口时表示随机
--expose <容器端口> # 指定暴露一个容器端口,但不映射到宿主机
-v [<宿主路径|卷名称>:]<容器路径>[:ro|:rw] # 卷映射,宿主路径是以/开头的绝对路径,
卷名称不存在时自动创建新卷,容器路径不存在时自动创建,ro表示只读,默认是rw表示读写
--volumes-from <容器名> # 指定容器里的所有卷都将加入到新创建的容器里
--network <网络名> # 默认bridge,同一个非默认网络下的容器可以使用主机名相互访问,
如果设置为host表示和宿主机共用网络,容器内的端口都将直接绑定到宿主机而无需-p建立映射
--link <容器名>:<主机名> # 在新创建的容器里可以通过主机名访问另一个运行中的容器
--add-host <主机名>:<IP> # 指定主机名解析添加到/etc/hosts文件
--env <环境变量名>=<环境变量值> # 设置环境变量,-e 选项可以重复多次
--env-file <环境变量文件> # 指定环境变量文件,换行符分割
--user <用户名>
--workdir <工作目录>
--mac-address="xx:xx:xx:xx:xx:xx" # 指定MAC地址
--ip="x.x.x.x" # 指定IP地址
--rm # 容器停止时自动删除容器,当镜像提供的是一个命令而不是服务器的时候尤其适合
--restart=always|unless-stopped|on-failure # 指定容器的重启策略,
如果容器内运行的是服务器则应该指定重启策略
always表示任何时候只要docker daemon开着总是重启容器
unless-stopped表示除非在docker启动时(如果宿主机重启就是上次关机时)容器就是stop状态,否则总是重启容器
on-failure表示只在容器以非0状态结束时自动重启

关于资源限制的参数

1
2
3
4
5
--cpuset-cpus="1,3" # 限制在核1、3上运行
--memory=1G # 限制内存最大使用1G
--device-read-bps /dev/sdd:40mb # 限制读取sdd的速度最大40mb
--device-write-bps /dev/sdd:30mb # 限制写入sdd的速度最大30mb
(机械硬盘读写速度是60-80MB,固态硬盘读写速度是130~300MB,都是写慢读快)

docker network 常用参数

1
2
3
4
5
6
7
create [--gateway=192.168.1.1] [--subnet=192.168.1.0/24] <网络名> # 创建一个新的网络
connect <网络名> <容器名> # 向一个网络中添加一个容器
disconnect <网络名> <容器名> # 从一个网络中移除一个容器
inspect <网络名> # 查看指定网络详细信息
ls # 查看网络列表
prune # 删除所有未使用的网络
rm <网络名> # 删除指定网络

docker inspect 常用参数

--format format的用法比较复杂,用几个逐级深入的例子演示,事实上我通常把docker_ip, docker_volume设成Linux别名方便使用

docker_id 查看容器的Id

1
docker inspect --format='{{.Id}}' 容器名

docker_ip 查看容器在所有网络中分配的所有的ip

1
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' 容器名

docker_volume 查看容器的卷

1
docker inspect --format='{{range .Mounts}}{{.Source}}:{{.Destination}}{{println}}{{end}}' 容器名

docker_port 查看容器所有的端口映射情况,用docker inspect实现 docker port命令

1
docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{range $conf}}{{$p}} -> {{.HostIp}}:{{.HostPort}}{{println}}{{end}}{{end}}' 容器名

docker volume 常用参数

1
2
3
4
5
6
7
create [卷名] # 创建一个卷,一般不用这个命令,因为在docker run -v [卷名:]容器路径 会自动创建
inspect 卷名 # 查看卷的详细信息
ls # 列出所有卷
prune # 删除当前未被使用的卷,最常用的是这个命令
rm 卷名 # 删除指定的卷


允许普通用户使用docker命令

1
usermod -aG docker <username>

容器内访问卷的权限

如果容器以非root用户启动,卷中的文件在宿主机设置怎么样的权限可以被容器内的普通用户访问呢,一般容器内的普通用户的UID是1000,在宿主机上可以对UID是1000的用户授权,无论容器内和宿主机的UID是1000的用户名是否相同。

构建镜像的一些技巧

Dockerfile中使用CMD指定容器启动时执行的脚本

1
CMD ["bash", "/root/setup.sh"]

如果容器提供是一个服务器,则脚本不应结束,一旦脚本结束则容器也会结束,如果服务后台运行,需要把前台卡住,可以使用

1
tail -f /dev/null

放在setup.sh的结尾。

另外我们可以选择启动脚本发生错误是直接退出,还是带着错误继续运行而不让容器退出。

在setup.sh的开始执行

1
set +e

表示脚本将无视错误继续执行,这也是默认行为

1
set -e

表示脚本将在遇到错误时立即退出,同时容器也将停止(即启动失败)

Dockerfile样例

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
FROM daimingzhuang/ubuntu:18.04
MAINTAINER DaiMingzhuang "kyo86.dai@gmail.com"
ENV REFRESHED_AT=2022-10-21
RUN apt update -yqq \
&& apt install -y vim openjdk-8-jre-headless python3.6 python3-pip libpqxx-dev cron redis-server nginx
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV JRE_HOME=$JAVA_HOME/jre
ENV CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$PATH:$JAVA_HOME/bin

ADD requirements.txt /root/requirements.txt
RUN pip3 install -r /root/requirements.txt

ADD zookeeper-3.4.14.tgz /opt/
ADD kafka_2.12-2.3.1.tgz /opt/
# 添加kafka配置文件
ADD kafka-server.properties /opt/kafka_2.12-2.3.1/config/server.properties
# 添加zookeeper配置文件
ADD zoo.cfg /opt/zookeeper-3.4.14/conf/zoo.cfg
# 添加zookeeper启动脚本
ADD zk_server_start.sh /opt/zookeeper-3.4.14/bin/zk_server_start.sh
# 添加redis配置文件
ADD redis.conf /etc/redis/redis.conf
# 添加nginx服务器配置
ADD nginx.conf /etc/nginx/nginx.conf
# 添加nginx站点配置
ADD nginx-default /etc/nginx/sites-available/default

ADD loop_run /usr/local/bin/supervised_run
ADD setup.sh /root/setup.sh
WORKDIR "/root/"
CMD ["bash", "/root/setup.sh"]

setup.sh样例

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
set +e
service cron start
service redis-server start
nohup supervised_run /opt/zookeeper-3.4.14/bin/zk_server_start.sh >/dev/null 2>&1 &
sleep 5
nohup supervised_run /opt/kafka_2.12-2.3.1/bin/kafka-server-start.sh /opt/kafka_2.12-2.3.1/config/server.properties >/dev/null 2>&1 &
sleep 5
nginx -c /etc/nginx/nginx.conf
tail -f /dev/null

构建镜像命令

1
docker build <imagename>:<tag> .

注意最后的. 表示存放Dockerfile的目录

docker login 失败解决

1
apt purge golang-docker-credential-helpers

为启动中的容器增加卷或端口映射

查看并记录容器的运行参数,主要包括name/user/卷映射/端口映射

1
docker inspect <container-name-or-id>

上面步骤如果不清楚要记录什么,也可以用工具自动生成run命令

生成容器的run命令

1
2
pip install runlike
runlike <container-name-or-id>

停止需要增加卷或端口映射的容器

1
docker container stop <container-name-or-id>

将现有容器制成镜像

1
docker commit <container-name-or-id> <new-image-name>

重命名旧容器(如果新容器希望沿用旧名字,并且不想立刻删除旧容器,以便新容器有问题时重新启用旧容器)

1
docker rename <container-name> <container-new-name>

用新镜像启动容器,用之前生成的run命令并带上需要新增的卷映射或端口映射

1
docker run -v xxx:xxx -p xx:xx <new-image-name>

确认无误后删除旧容器

1
docker rm <container-name-or-id>

docker配置文件

/etc/docker/daemon.json

默认是空的配置,可以自己增加项目,例如修改docker的根路径和修改镜像源。

1
2
3
4
{
"data-root": "/data/docker",
"registry-mirrors": ["https://eyl5ycru.mirror.aliyuncs.com"]
}

导出镜像

1
docker save <镜像名>:<标签名> -o <tar文件路径>

导入镜像

1
docker load -i <tar文件路径>

docker-compose

docker-compose可以把多个相互配合容器(通过网络或共享卷)的启动参数写在一个yml文件里。或者只是把一个容器的启动参数固定在yml文件里。

docker-compose.yml样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3'
services:
clickhouse:
image: clickhouse/clickhouse-server
container_name: clickhouse
ulimits:
nofile:
soft: 262144
hard: 262144
ports:
- "8123:8123"
- "9000:9000"
volumes:
- /data/clickhouse-data/data/:/var/lib/clickhouse/
- /data/clickhouse-data/log/:/var/log/clickhouse-server/
restart: unless-stopped
environment:
- CLICKHOUSE_USER=default
- CLICKHOUSE_PASSWORD=default@clickhouse

创建并启动

1
docker-compose up -d

停止并删除

1
docker-compose down

在Shell脚本中输入密码的几种情况

如果是要使用sudo

首先在Shell脚本中使用sudo不是一个好主意。

可以考虑的替代方案有,

  1. 在脚本中去掉sudo,而要求在运行脚本时使用sudo。
  2. 如果是任务计划,考虑配置在root用户下。

在确定要在Shell中sudo的情况下可以使用如下方式

1
echo '密码' | sudo -S 命令 

如果是通过ssh/autossh建立远程连接或建立tunnel

考虑用SSH密钥登录来避免使用密码

例如我们要从 hostA 通过ssh密钥登录 hostB

我们现在 hostA 上生成密钥

1
ssh-keygen -t rsa

会询问将密钥放在何处,默认即可。然后是输入密码,留空(否则你登录不仅需要私钥还要输入密码)。

完成后在~/.ssh目录下会生成两个文件id_rsa和id_rsa.pub,一个私钥一个公钥。

把公钥追加到 hostB 某个用户的 ~/.ssh/authorized_keys 就代表 hostB 的这个用户允许持有相应私钥登录

ssh-copy-id命令可以帮我们完成追加公钥的操作

命令格式:ssh-copy-id [-p SSH端口默认22] [user@]hostname

在 hostA 上执行

1
ssh-copy-id root@hostB

按提示输出密码即可

还需要确保 hostB 的sshd服务配置允许使用公钥授权

配置文件路径 /etc/ssh/sshd_config

确认

1
PubkeyAuthentication yes

如果修改过配置需要重启sshd服务

1
service sshd restart

在hostA上现在可以验证登录hostB不再需要输入密码

1
ssh root@hostB

其他情况或其他程序需要输入密码

可以用expect工具,事实上这种方法也可以应对上面的几种情况

使用autossh+expect建立ssh tunnel的用法举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

passwd='123456'

/usr/bin/expect <<-EOF

set time 10
spawn autossh -M 0 -CNg -L 1433:192.168.1.140:1433 root@$WAN_IP -p $WAN_PORT
expect {
"*yes/no" { send "yes\n"; exp_continue }
"*password:" { send "$passwd\n" }
}
expect eof
EOF

expect更详细的说明自行搜索 linux expect

1
2
3
4
git clone --mirror <原地址>   # 会取得 项目名.git 的目录,里面是包含了提交历史、分支等的完整项目库
cd <项目名.git>
git remote set-url --push origin <新地址> # 新地址是提前创建好的(空白的)新项目库路径
git push --mirror

告知其他合作开发者修改远程库路径到新地址

1
git remote set-url origin <新地址>
1
git remote -v # 可以查看当前设置的地址 

df.to_excel底层默认优先用的是xlsxwriter,sheet对象的类型是 xlsxwriter.worksheet.Worksheet,它没有column_dimensions属性。
可以通过在创建ExcelWriter的时候指定engine="openpyxl"让底层使用openpyxl,sheet对象的类型会变成openpyxl.worksheet.worksheet.Worksheet,它是有column_dimensions属性的。
需要安装过 openpyxl 库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
with pd.ExcelWriter(f"xxx.xlsx",
engine="openpyxl",
date_format="YYYY-MM-DD",
datetime_format="YYYY-MM-DD HH:MM:SS"
) as writer:
df.to_excel(writer)
sheet = writer.sheets["Sheet1"]
# 设置列宽
sheet.column_dimensions['A'].width = 12.5
# 设置行高
sheet.row_dimensions[1].height = 72
# 设置表头(第一行)自动换行
r = sheet[1]
for c in r:
c.alignment = openpyxl.styles.Alignment(wrapText=True)
# 设置表体数字格式显示4位小数
for i, r in enumerate(sheet):
for j, c in enumerate(r):
if i != 0 and j != 0:
c.number_format = "0.0000"

问题描述

周末办公楼电力系统维护停电3小时,服务器关机,电力恢复后服务器启动失败,现象是启动报错,提示让输入密码进入紧急模式。
看到主要的错误描述是:
CIFS VFS: ioctl error in smb2_get_dfs_refer rc=-5
怀疑和mount有关。
输入密码进入紧急模式,输入命令

1
mount -a

即根据配置文件/etc/fstab进行挂载(正常启动时也是根据此文件进行挂载)
其中/etc/fstab的内容如下

1
2
3
4
……省略若干
/dev/md126p1 /data ext4 defaults 0 0
/dev/sdd1 /data2 ext4 defaults 0 0
……省略若干

执行后看到了具体的挂载错误条目是找不到设备 /dev/sdd1
使用命令

1
fdisk -l

可以看到存在/dev/sdd和/dev/sdd1

1
2
3
4
5
6
7
8
9
Disk /dev/sdd: 3.7 TiB, 4000787030016 bytes, 7814037168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 737D5D12-456A-414A-86DC-0F8279EDA29B

Device Start End Sectors Size Type
/dev/sdd1 2048 4294969343 4294967296 2T Linux filesystem

使用命令

1
lsblk

列出块设备信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
loop0 7:0 0 99.4M 1 loop /snap/core/11993
loop1 7:1 0 99.5M 1 loop /snap/core/11798
sda 8:0 0 931.5G 0 disk
├─sda1 8:1 0 512M 0 part
└─sda2 8:2 0 931G 0 part
sdb 8:16 0 1.8T 0 disk
├─sdb1 8:17 0 512M 0 part /boot/efi
└─sdb2 8:18 0 1.8T 0 part /
sdc 8:32 0 1.8T 0 disk
└─sdc1 8:33 0 1.8T 0 part /data2
sdd 8:48 0 3.7T 0 disk
└─md126 9:126 0 3.7T 0 raid0
└─md126p1 259:0 0 2T 0 md /data
sr0 11:0 1 1024M 0 rom

发现sdd下面是md126 md126p1,想起来有一块盘做成了raid0,挂载时不能按照sda、sdb、sdc的命名方式挂载,需要用/dev/md126p1,但这不是问题,因为fstab文件中确实使用了/dev/md126p1,但不应该挂载/dev/sdd1了啊,其实是这次重启之后原来的sdd1变成了现在的sdc1,可能因为上次的磁盘是热插入的,重启之后根据插入口的顺序重新分配了设备名,把fstab文件中的sdd1改成sdc1,重新mount -a成功,进入/data2查看文件,确实是之前sdd1上的。

重启会重新分配设备名导致挂载失败的问题,根据linux下磁盘sda,Linux下磁盘设备文件(sda,sdb,sdc….)变化问题_林声飘扬的博客-CSDN博客这篇博文的说法,目前没有办法直接解决,但是可以通过指定id和uuid的方式挂载而不使用sda、sdb这种名称来绕过这个问题

使用

1
2
ls -la /dev/disk/by-id
ls -la /dev/disk/by-uuid

可以看到这些id和uuid就是指向sda、sdb之类的软连接,而id和uuid每次启动是固定的,那么这些软连接应该是在启动过程中生成的。
最后保险起见,我将fstab文件中的挂载设备改成了id表示

1
2
# /dev/sdc1       /data2        ext4    defaults 0      0
/dev/disk/by-id/ata-ST2000NM0055-1V4104_ZC20W9TN-part1 /data2 ext4 defaults 0 0

SSH Tunnel解决的问题是让原本不能被访问的端口(通常是因为在不同局域网)可以被访问。

SSH Tunne按监听端口是本地还是远程分为两种命令,格式分别如下:

在本地端口(bind_address:port,指定bind_address是因为本机可能有多个网络接口,缺省在localhost上监听,只接受来自本机的链接,接受任意需指定为*)上开启监听,将收到的数据通过tunnel转发到(hostname:22),在由hostname转发到host:hostport

1
ssh [-L [bind_address:]port:host:hostport] [user@]hostname -p 22

在远程端口(bind_address:port)上开启监听,将收到的数据通过tunnel转发到本机,再由本机转发到(host:hostport)

1
ssh [-R [bind_address:]port:host:hostport] [user@]hostname -p 22

用法举例

假设A要访问D:6379,A、D在不同局域网,最简单方法是在D所在的局域网网关上配置端口映射把D:6379映射到公网,但是很多时候出于某些因素(例如:安全考量、没有配置网关的权限、服务本身配置了只允许本地访问),不希望或无法通过把D:6379映射到公网解决,就可以考虑使用SSH Tunnel。

情形1:

D所在的局域网有主机C的22端口是被映射到公网的,不妨假设映射成了X:30022,X是C和D所在局域网网关在公网的IP地址,那么可以在主机A上通过命令

1
HostA$ ssh -L 36379:D:6379 root@X -p 30022

把D:6379映射成了A:36379,特别情况C和D是同一台主机,D可以写成localhost。

情形2:

A所在的局域网有主机B的22端口是被映射到公网的,不妨假设映射成了X:30022,X是A和B所在局域网网关在公网的IP地址,那么可以在主机D上通过命令

1
HostD$ ssh -g -R 36379:D:6379 root@X -p 30022

把D:6379映射成了B:36379,而A和B是同一局域网的,因此A可以访问到B:36379。

按理说上面的命令加了-g参数,B:36379应该被绑定在0.0.0.0:36379,但实际测试发现绑定在了127.0.0.1:36379,导致无法在A上访问,原因暂时未知(确定不是因为redis配置了保护模式),不过可以通过类似情形1的方式再把B:36379映射成A:36379,这样A就可以访问了。

1
HostA$ ssh -L 36379:localhost:36379 root@B

情形3:

A和D所在局域网都没有主机的ssh端口被映射到公网,但我有另一台具有公网IP的主机,不妨假设为X开启着22端口,类似情形2通过命令

1
HostD$ ssh -R 36379:D:6379 root@X
1
HostA$ ssh -L 46379:localhost:36379 root@X

之后让A访问A:46379即可。

通过命令建立SSH Tunnel之后会登录到作为跳板的机器,通常我们是不需要使用这个控制台的,因此可以通过-f参数让ssh在后台运行,需要关闭tunnel时直接杀死进程。

常用选项

C表示压缩数据传输

f表示后台用户验证,这个选项很有用,没有shell的不可登陆账号也能使用.

N表示不执行脚本或命令

g表示允许远程主机连接转发端口

例如情形1中的命令可以写成

1
ssh -CfNg -L 36379:D:6379 root@X -p 30022

通过

1
ps -ef|grep ssh -CfNg

可以查看打开的tunnel。

保持ssh连接(防止超时自动断开)

在客户端设置/etc/ssh/ssh_config

1
2
3
Host *
ServerAliveInterval 300
ServerAliveCountMax 5

使用autossh

当网络不稳定时还可以使用autossh工具来帮助自动重连。

只需要把ssh命令中的ssh换成autossh -M

例如情形1中的命令可以写成

1
autossh -M 46379 -CfNg -L 36379:D:6379 root@X -p 30022

-M 后面的端口号是用来监视连接状态的,允许指定为0,这里指定为0是否可以正常监视重连以及如何监视有待研究。

另外如果autossh带了-f参数则不支持输入密码,可以配合expect脚本自动输入密码或者通过SSH密钥登录,更推荐使用密钥方式。

生成密钥

1
ssh-keygen -t rsa

会询问将密钥放在何处,默认即可。然后是输入密码,留空(否则你登录不仅需要私钥还要输入密码)。

完成后在~/.ssh目录下会生成另个文件id_rsa和id_rsa.pub,一个私钥一个公钥。

然后将公钥写入远程用户家目录下的~/.ssh/authorized_keys文件中,通过ssh-copy-id命令可以帮我们实现这一操作(相当于把公钥复制过去再追加到authorized_keys的尾部)

1
ssh-copy-id [-p SSH端口默认22] [user@]hostname

参考:

https://blog.csdn.net/wxqee/article/details/49234595

https://www.cnblogs.com/youxin/p/5220916.html

https://blog.csdn.net/wesleyflagon/article/details/85304336

假设后端服务器跑在backend-server:30101,跑在本地就是localhost:30101

开发环境下通过 config/index.js 配置代理

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
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/view/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: "http://backend-server:30101", // 测试环境下配置端口转发
changeOrigin: true,
pathRewrite: {
"^/api": "", // 重写路径,去掉前缀api
}
}
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}

生产环境下通过前端所在服务器上的Nginx配置代理在
/etc/nginx/sites-enabled/default
中添加 location ^~/api/ 对应的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
# ......

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

location ^~/api/ {
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;
proxy_read_timeout 300;

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

# ......
}

需求情景:

存放日志或接收并存储数据的目录,为了防止程序出错时疯狂写日志硬盘被日志或存储的数据塞满。

原理,创建一个固定大小的img文件,映射成一个目录

1
2
3
4
5
6
7
8
# 创建Log.img大小1G
dd if=/dev/zero of=./Log.img bs=1M count=1024
# 格式化 ext4
mkfs.ext4 ./Log.img
# 创建挂载目录
mkdir ./Log
# 挂载
sudo mount -o loop ./Log.img ./Log

搭建PPTP服务器

参考:https://blog.csdn.net/sanve/article/details/80882731

安装

1
apt install pptpd

配置IP

1
vim /etc/pptpd.conf

查找并解开这3处的注释并修改相应配置

1
2
3
#bcrelay eth1 
#localip 192.168.10.1
#remoteip 192.168.10.234-238,192.168.10.245

bcrelay的意思我理解是来自虚拟局域网的广播要从哪个物理网卡转发出去

localip的意思本机是VPN服务器的IP地址

remoteip的意思是当有远程VPN客户端连接上来时被分配的IP段

我认为应该把虚拟局域网的网段和物理局域网分开,而且localip和remoteip应该在同一个网段,网上有localip和remoteip不应该在同一个网段的说法我并不认同。

配置DNS

1
vim /etc/ppp/pptpd-options 

找到ms-dns解开注释并修改相应配置

1
2
ms-dns 114.114.114.114
ms-dns 8.8.8.8

配置用户名密码

1
vim /etc/ppp/chap-secrets 
1
2
3
# Secrets for authentication using CHAP
# client server secret IP addresses
<username> pptpd <password> *

修改配置后重启PPTP服务器

1
service pptpd restart 

打开IPv4转发

1
vim /etc/sysctl.conf 

找到并修改或添加

1
net.ipv4.ip_forward = 1 

使配置生效

1
2
sysctl -p service
procps restart

在网关上添加指向VPN网段的静态路由,设置下一跳地址为架设VPN服务器主机的IP,这样整个局域网都是可以通过VPN正常访问的。

如果没有操作网关的权限,也可以在需要访问的主机上添加静态路由

1
ip route add 192.168.10.0/24 via 192.168.1.99 

这里192.168.1.99就是架设VPN服务器的主机IP

Ubuntu PPTP客户端

参考:https://www.jianshu.com/p/1680c721f397 http://www.linuxfly.org/post/641/

1
pptpsetup --create 连接名 --server VPN服务器地址 --username 用户名 --password 密码 --encrypt

启动VPN连接

1
pon 连接名

关闭VPN连接

1
poff 连接名

查看路由表

1
route -n

添加路由,将通过VPN接入的物理局域网网段路由指定通过ppp0网口转发

1
route add -net 192.168.1.0 netmask 255.255.255.0 dev ppp0 

每次重连后路由会丢失,需要重新配置,可以用下面的方法在ppp0 up时自动添加路由

pptp连接时自启动添加路由

参考:https://blog.csdn.net/qq_27434019/article/details/102920504

1
vim /etc/ppp/peers/连接名 

增加ipparam一行

1
ipparam 连接名 

下面这步似乎不必要,不清楚作用

1
vim /etc/network/interfaces 

增加

1
2
3
auto tunnel
iface tunnel inet ppp
provider 连接名

======================================

新建脚本文件并修改权限

1
2
touch /etc/ppp/ip-up.d/连接名
chmod a+x /etc/ppp/ip-up.d/连接名

编辑脚本

1
vim /etc/ppp/ip-up.d/连接名 

在脚本中加入添加路由的语句

1
route add -net 192.168.1.0 netmask 255.255.255.0 dev ppp0

可以poff 再 pon 再 route -n 看看路由是否自动添加

服务器其他用户反馈读取数据库很卡。

通过 sar -d -p 3 命令发现硬盘占用率比较高

通过 iotop 命令发现主要是被一个名为 [jbd2/sda2-8] 的进程占用

网上说法是 (13条消息) 性能分析之IO分析-jbd2引起的IO高_hualusiyu的专栏-CSDN博客

1
jbd2的全称是journaling block driver 。这个进程实现的是文件系统的日志功能,磁盘使用日志功能来保证数据的完整性。这个需要评估一下安全和性能哪个更重要,解决方案是升级内核或者牺牲完整性来换性能。

差点被误导。

而使用命令 atop -d 发现其实是 snapd 占用

和这篇帖子情况一样 snapd持续运行,引起jbd2/sda2-8持续访问硬盘,占用大量io - Ubuntu中文论坛

1
2
3
[#6](https://forum.ubuntu.org.cn/viewtopic.php?p=3221983#p3221983)
帖子 由 sffred » 2020-06-06 16:04
我最终解决这个问题的方式是卸载snapd。反正我也用不着

snapd是ubuntu预装的一个软件包管理工具。

使用 snap list 发现只有一个core,也就是我没有基于snap安装过软件包。

通过 service snapd stop 关闭snapd,再通过 sar -d -p 3 观察硬盘占用,已经完全正常

至此确定是由snapd引发.

通过service snapd start 启动snapd,观察硬盘占用,先是再次上升数十秒后回到了正常。

如果下次再出现占用过高准备禁用或卸载snap。

禁用 systemctl disable snapd.service 卸载 apt purge snapd

相关命令

持续观察硬盘读写情况,每3秒刷新一次

1
sar -d -p 3

sar常用的的参数还有监控CPU情况的

1
sar -u 3

按IO从高到低排序监控进程,实时刷新

1
iotop

也能按IO从高到低排序,实时刷新,感觉比iotop好用

1
atop -d

atop也有监控CPU情况的

1
atop -u