0%

前段见时间有个项目,自己想尽可能查看现场各设备的运行参数,显示一些统计信息,之前同事用C#写了个画面的,但感觉不灵活,换个项目,要改的东西就很多,界面基本需要重画。后来想起之前用ECharts做过电子在屏的,应该展示效果会更好。之前考虑用React做前端的,后来想想就一个页面,直接用Vue干吧,正好Vue也升级到3了,然后用Electron做个包装,也不用再下浏览器。记录下碰到的问题吧。

ElementUI按需引入

之前Vue用的脚手架是vue-cli,统计时间是比较长的,升级到3之后,改用vite了,很方便。因为使用的组件不多,把ElementUI全部引入的话,JS文件会比较大,所以只引入使用的几个组件。参照官方文档

main.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
import 'element-plus/lib/theme-chalk/index.css'

import {
ElButton,
ElCol,
ElInputNumber,
ElProgress,
ElRow,
ElDescriptions,
ElDescriptionsItem,
} from 'element-plus';

const plugins = [
ElRow,
ElCol,
ElButton,
ElDescriptions,
ElDescriptionsItem,
ElInputNumber,
ElProgress
]

const app = createApp(App)

plugins.forEach(plugin => {
app.use(plugin)
})

app.config.globalProperties.$ELEMENT = { size: 'small', zIndex: 3000 }

app.mount('#app')

ECharts

按需引入

ECharts升级到5之后,与之前的引入有一点区别,具体可参考官方文档

linechart.vue
1
2
3
4
5
6
7
import * as echarts from 'echarts/core'
import { BarChart, LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers' // CanvasRenderer, SVGRenderer
import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components'
// require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
import moment from 'moment'

VUE中引入ECharts

最开始在VUE中引入ECharts老是报错,后来查了下,应该是DOM没有准备好,具体参考的应该是vue-element-admin里面的配置来着。应该用nextTick(),待DOM准备好之后,ECharts绑定到DOM。

LineChart.vue
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
data() {
this.chart = null
return {
// chart: null
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
methods: {
initChart() {
echarts.use([BarChart, LineChart, GridComponent, TooltipComponent, TitleComponent, CanvasRenderer])
this.chart = echarts.init(this.$el) // 因为这个组件只有一个div展示图片,所以用this.$el,如果是其它,可使用this.refs..$el
this.chart.setOption(
{
title: {
text: 'title'
},
grid: [
{
left: 50,
right: 50,
bottom: 0,
containLabel: true
},
],
axisPointer: {
link: {xAxisIndex: 'all'}
},
xAxis: [
{
// id: 'x1', 解决内存泄漏
type: 'category',
boundaryGap: true,
axisPointer: {
type: 'shadow'
},
splitLine: {
show: true,
interval: 9,
lineStyle: {
type: "dashed",
color: "rgba(0, 0, 0, 1)",
shadowBlur: 2
}
}
},
{
//id: 'x2',
type: 'category',
boundaryGap: true,
position: 'top',
axisLabel: {
show: false
}
}
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
animation: false,
}
},
yAxis: [
{
//id: 'y1',
type: 'value',
name: 'y1',
max: 900,
min: 0,
interval: 100,
axisLabel: {
formatter: '{value} °C'
},
axisTick: {
show: true
},
axisLine: {
show: true
},
splitLine: {
show: true
}
},
{
//id: 'y2',
type: 'value',
name: 'y2',
axisLabel: {
formatter: '{value} min'
},
axisTick: {
show: true
},
axisLine: {
show: true
},
splitLine: {
show: false
}
},
{
//id: 'y3',
type: 'value',
name: 'y3',
inverse: true,
min: 0,
show: false
}
],
series: [
{
//id: 's1',
name: 's1',
type: 'bar',
},
{
//id: 's2',
name: 's2',
type: 'line',
yAxisIndex: 1,
},
{
//id: 's3',
name: 's3',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 2,
}]
}
)
this.chart.on('click', 'series', params => {
this.$emit('click', parseInt(params.name))
})
},
}

内存泄漏

参照ECharts的样例做好了一个动态更新的图表,每秒从后台拉下数据进行更新,还蛮好看的。可是过两天后发现浏览器或者Electron内存暴了,直接把系统快干死。当时不清楚哪里的问题,以为是每秒获取数据后处理有问题,检查了好久都没办法解决,最后只好排除法,页面上的东西一个个排除观察一段时间看内存是不是一直往上涨,最后定位到ECharts。上网查了下,发现有提到的echarts.clear()echarts.dispose(),感觉这个有点不靠谱,这样子那不是每次echarts都需要重新生成?反正也试了,确实效果不好,图表都是重新生成,不能连续变化。

没办法,查文档吧,最后还真发现了些什么。里面有个组件合并模式,就是每次数据变化后都会调用一次this.echarts.setOption(),因为设置不当,数据一直往图表里面填,没有释放旧数据。其实解决办法很简单,就是把有变动的地方设置好ID,每次更新就更新带ID的数据。

LineChart.vue
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
this.chart.setOption(
{
xAxis: [
{
id: 'x1',
data: xArray
},
{
id: 'x2',
data: xArray,
}
],
yAxis: [
{
id: 'y2',
max: Math.max(...stayTimes.filter(e => !isNaN(e))),
min: Math.min(...stayTimes.filter(e => !isNaN(e))),
},
{
id: 'y3',
max: Math.max(...stayTimesDelta.filter(e => !isNaN(e))) * 10,
}
],
series: [
{
id: 's1',
data: chargeTemps
},
{
id: 's2',
data: stayTimes
},
{
id: 's3',
data: stayTimesDelta
}]
}
)

想法

之前家里是光纤通过运营商的光猫桥接,然后一个路由器拨号,再连一个交换机。因为之前已经搞了台NAS,之前一直想整台HP Gen8的,有iLO管理口,再加上eSXi虚拟化,搞个软路由和NAS,这样比较完美。后面没买到便宜的Gen8,现在考虑功耗比较大,软Raid导致噪音比较大,就算了没整Gen8了。看看后面能不能换个大House,整个小的机柜,再搞个服务器玩玩吧。

现在路由器、交换机和NAS在一个小小的工具箱里面,夏天温度有点高,想想看把软路由整到NAS里面去吧。NAS用的OS是OMV5,基于Debian,比较熟悉,整个虚拟机安装OpenWrt应该是一个可行的方案。KVM一直不是太熟悉,直接用VirtualBox,之前VirtualBox没用过CLI,正好供机会学习下CLI控制。

这样的好处就是不用再单独开一个硬件路由器了,减少了一点点电费和热量,然后性能会强一些;缺点就是Host OS挂掉的话,就全屋不能上网了。

主要参考三篇官方文章OpenWrt on VirtualBox HowTo,VirtualBox Advanced,VirtualBox Manual

构架

NAS主板有一个千兆电口,然后自己备了一个PCI双网卡,但是因为主板的原因,PCI网卡的最高速率好像只有400M,所以想的就是一个PCI的口连ISP,板载千兆电口连交换机,多一个口备用。

Host OS Debian里面将板载网卡(enp1s0)设置成静态IP(192.168.1.2/32),PCI网卡(enp6s0/enp7s0)设置成启用(否则网卡为Down状态,无法使用)。

OpenWrt三网口,管理口(eth0),Hostonly,用于Host访问;WAN口(eth1),桥接物理网口,连接ISP网口;LAN口(eth2),桥接物理网口,连接交换机。大致示意图可参照OpenWrt说明

Networking Example

安装Virtual Box

官方文档安装VirtualBox和Extension Pack,另外需要安装编译工具和对应头文件,dkms,build-essential, linux-headers-$(uname -r),将用户加入用户组usermod -a -G vboxusers user

有一点就是如果内核有升级的话,VirtualBox就需要重新设置下/usr/sbin/vboxconfig,所以非必要情况下不升级内核,apt-mark hold linux-image-amd64 linux-headers-$(uname -r)

安装Extension Pack,VBoxManage extpack install ext.vbox-extpack

检查服务vboxautostart-service是否自动启动,这样Host启动时,会自动启动对应的vm。

安装OpenWrt

下载对应OpenWrt镜像,参考官方地址

创建vm

1
2
3
4
5
6
7
mkdir -p ~/vbox
cd ~/vbox
VBoxManage createvm openwrt --ostype Linux26_64 --registervm --default --basepath `pwd`
cd ~/vbox/openwrt
wget -c https://downloads.openwrt.org/releases/19.07.8/targets/x86/64/openwrt-19.07.8-x86-64-combined-ext4.img.gz
gzip -d openwrt-*x86-64-combined*.img.gz
VBoxManage convertfromraw --format VDI openwrt-*x86-64-combined*.img openwrt.vdi

VirtualBox创建一个Linux vm,设置基础目录为~/vbox/openwrt,下载对应镜像,并转换为VirtualBox可用的文件。

修改vm配置

1
2
3
4
5
6
VBoxManage hostonlyif create
VBoxManage hostonlyif ipconfig vboxnet0
VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.56.1 --netmask 255.255.255.0
VBoxManage dhcpserver add --network=vboxnet0 --server-ip=192.168.56.1 --lower-ip=192.168.56.101 --upper-ip=192.168.56.254 --etmask=255.255.255.0 --enable

VBoxManage modifyvm openwrt --memory 128 --boot1 disk --boot2 none --boot3 none --nic1 hostonly --nictype1 virtio --hostonlyadapter1 vboxnet0 --nic2 bridged --nictype2 virtio --bridgeadapter2 enp6s0 --nic3 bridged --nictype3 virtio --bridgeadapter3 enp1s0 --mouse ps2 --keyboard ps2 --audio none --usb off --usbehci off --usbxhci off --vrde on --vrdeport 10001 --audostart-enabled of --autostart-delay 120

这里主要是创建一个hostonly网络,并设置好IP,DHCP等,Host的IP是192.168.56.1,等下Openwrt里面再设置管理口的IP。然后修改openwrt vm的内存为128,只从硬盘启动,网卡1为hostonly管理网卡,网卡2为bridged的WAN(enp6s0)口,网卡3为bridged的LAN(enp1s0)口。然后关闭声卡,USB。

VBoxManage Manual

挂载OpenWrt镜像

1
2
VBoxManage showvminfo openwrt | grep 'Storage Controller'
VBoxManage storageatt openwrt --storagectl 'SATA' --device 0 --port 0 --type hdd --medium ~/vbox/openwrt/openwrt.vdi

正常默认情况下有个IDE和SATA,IDE用于光驱,SATA用于硬盘使用。然后将openwrt的镜像挂载为硬盘。

虚拟机随host自动启动

参考Manual,AutoStart VirtualBox VMs on System Boot on Linux

1
VBoxManage modifyvm openwrt --autostart-enabled on --autostart-delay 10

远程控制vrde

参考Manual

1
VBoxManage modifyvm openwrt --vrde on --vrde-port 10001

这样就可以通过Host OS的10001端口进行远程操作,直接使用windows的mstsc可以连接,linux的rdesktop。

OpenWrt网络配置

前面配置好OpenWrt后,先将网卡设置成断开,然后启动OpenWrt,防止网络冲突啥的。

1
2
3
VBoxManage controlvm openwrt poweroff
VBoxManage modifyvm openwrt --cableconnected1 off --cableconnected2 off --cableconnected3 off
VBoxManage startvm --type headless

然后远程连上10001端口进行网络设置,将三张网卡分别设置好。

/etc/config/network
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
config interface 'loopback'
option ifname 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'

config globals 'globals'

config interface 'lan'
option ifname 'eth2'
option proto 'static'
option netmask '255.255.255.0'
option ip6assign '60'
option ipaddr '192.168.1.1'

config interface 'wan'
option ifname 'eth1'
option proto 'pppoe'
option password 'password'
option ipv6 'auto'
option username 'username'

config interface 'mng'
option proto 'static'
option netmask '255.255.255.0'
option ifname 'eth0'
option ipaddr '192.168.56.2'

配置DHCP,主要是为了使用IPv6,见lan配置处。

/etc/config/dhcp
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
config dnsmasq
option domainneeded '1'
option localise_queries '1'
option rebind_protection '1'
option rebind_localhost '1'
option local '/lan/'
option domain 'lan'
option expandhosts '1'
option authoritative '1'
option readethers '1'
option leasefile '/tmp/dhcp.leases'
option resolvfile '/tmp/resolv.conf.auto'
option localservice '1'
option confdir '/tmp/dnsmasq.d'

config dhcp 'lan'
option interface 'lan'
option start '100'
option limit '150'
option leasetime '12h'
option ra_management '1'
option ra 'server'
option dhcpv6 'server'
option ndp 'relay'

config dhcp 'wan'
option interface 'wan'
option ignore '1'

取消ipv6防火墙,禁止LAN口上网,需要授权才能使用。见OpenWrt限制设备连接Internet。如没有此需求,将forwarding段的enabled选项设置为1即可。

/etc/config/firewall
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
config defaults
option syn_flood '1'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'REJECT'
option disable_ipv6 '1'
option drop_invalid '1'

config zone
option name 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
list network 'lan'

config zone
option name 'wan'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'
list network 'wan'

config forwarding
option src 'lan'
option dest 'wan'
option enabled '0'

一切就绪后就可以将网卡连接上使用了。

1
2
3
VBoxManage controlvm openwrt poweroff
VBoxManage modifyvm openwrt --cableconnected1 on --cableconnected2 on --cableconnected3 on
VBoxManage startvm --type headless

团队去年将代码管理迁移到Gitlab后,尝试了CI/CD,用Gitlab-Runner来测试提交的代码是否有问题,用3台机器,kubeadm搭建了一个小的k8s运行环境。上个月25日突然反映runner不能正常运行,后台看了下是证书过期的原因,kubeadm v1.5后好像有证书更新的功能,之前用的好像是v1.2版本,证书更新起来不太方便。现在在做项目,没有时间去整理,还不如重新搭建算了。后来有时间的话,再尝试搭建k8s,the hard way,应该会对kubernetes更深一步的了解。

前段时间CentOS 8发布了,也试了下CentOS 8,但是各方软件兼容还存在一点问题,比如Docker,后来还是以CentOS 7为操作系统。vSphere里新建一台机器,各参数设置好后,再Clone两台,修改IP和hostname就OK,省一些系统安装的时间。

Docker准备

Docker安装比较简单,按官方指引来就OK,因其它原因国内下载Docker Image比较慢,需要一些简单配置,主要参考TUNAUSTC

1
2
3
4
5
6
wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
sudo sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
yum install yum-utils device-mapper-persistent-data lvm2
yum install docker-ce
gpasswd -a user docker
systemctl enable --now docker
/etc/docker/daemon.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"registry-mirrors": ["https://registry.docker-cn.com","https://docker.mirrors.ustc.edu.cn/"],
"insecure-registries": ["10.17.65.22:8088"],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"labels": "production_status",
"env": "os,customer"
},
"exec-opts": ["native.cgroupdriver=systemd"]
}

Kubeadm安装k8s

Kubeadm准备

Kubeadm安装参考官方文档。这次安装的版本是v1.16.2,版本不一样可能涉及到一些Docker Image不一样。可通过kubeadm config images list查看对应版本需要下载的镜像。

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
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
#gpgcheck=1
gpgcheck=0
EOF

# Set SELinux in permissive mode (effectively disabling it)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

systemctl enable --now kubelet

cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

systemctl stop firewalld
sudo systemctl disable firewalld

Docker Image准备

因为某些原因无法从外网下载镜像,需要手动处理下,这里要感谢下阿里,提供了一个镜像下载地址。

./k8s_mirror.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

images=(kube-apiserver:v1.16.2
kube-controller-manager:v1.16.2
kube-scheduler:v1.16.2
kube-proxy:v1.16.2
pause:3.1
etcd:3.3.15-0
coredns:1.6.2)
#flannel:v0.11.0-amd64

# or bluersw/image:version
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
#docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName 10.17.65.22:8088/$imageName
#docker push 10.17.65.22:8088/$imageName
done

写好脚本后,直接运行这个脚本,把需要的Image拉下来。

k8s节点

k8s的节点安装参考官方文档

1
2
3
4
5
sudo kubeadm init --kubernetes-version=v1.16.2 --apiserver-advertise-address=10.17.65.250 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.1.0.0/16
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
sudo chown liangwu:liangwu ~/.kube/config
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml

其它节点根据kubeadm init后的提示直接kubeadm join就好了。前提也是需要把一些镜像拉下来

  • k8s.gcr.io/pause:3.1
  • k8s.gcr.io/kube-proxy:v1.16.2
  • quay.io/coreos/flannel:v0.11.0-amd64

Gitlab-Runner Helm安装

安装Helm

参照官方文档,作简要配置。

rbac-config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system

然后运行kubectl create -f rbac-config.yaml。或者其它方式。主要是RBAC的问题。

1
2
3
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

Helm配置

Helm也涉及到一些被墙的东西,换下拉取地址。再次感谢阿里? :)

1
2
helm repo add gitlab https://charts.gitlab.io/
helm init -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.5.1 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts --service-account tiller --history-max 200

Gitlab Runner安装

Gitlab Runner的文档确实好像不怎么样,好多东西都没写,或者写得不好。

1
2
3
4
5
#values.yaml需要根据自己实际情况进行修改,各项都有说明
wget -c https://gitlab.com/gitlab-org/charts/gitlab-runner/raw/master/values.yaml?inline=false
helm fetch gitlab/gitlab-runner
tar xvf gitlab-runner-${version}.tgz
helm install --namespace gitlab --name gitlab-runner -f values.yaml ./gitlab-runner-${version}

values.yaml有以下几点需要注意的。

values.yaml
1
2
3
4
5
6
7
8
9
imagePullPolicy: IfNotPresent
gitlabUrl: "http://10.17.65.22:5622/gitlab"
#注册新runner
runnerRegistrationToken: "xxxx"
#已有runner的token
runnerToken: "D1zsss2nS6Lgkx5M4_zx"
runners:
#从私有docker registry中拉取image时需要的认证信息,可参考原values.yaml中的k8s官方链接说明
imagePullSecrets: ["regcred"]

我们Gitlab CI/CD中有个验证就是利用maven编译代码是否可通过,其中需要拉取很多供事的jar包,每次都拉取比较耗时,虽然都从局域网的私服拉。可将.m2的缓存存在node本机,小的k8s就这么做吧。懒得去设置PV,PVC了。以后需要大的k8s,再去整PV吧。NFS啊,MinIO之类的。在helm包文件里修改templates/configmap.yaml,在start gitlab runner之前加入以下内容。已经在Gist中加入。

gitlab-runner/templates/configmap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### ...

cat >>/home/gitlab-runner/.gitlab-runner/config.toml <<EOF
[[runners.kubernetes.volumes.host_path]]
name = "m2"
mount_path = "/root/.m2"
host_path = "/m2"
[[runners.kubernetes.volumes.host_path]]
name = "cache"
mount_path = "/cache"
host_path = "/cache"
EOF

### ...

下面是配置文件详情。

之前一直通过SS使用VPS的IPv6是可以使用bt.byr.cn的,但是从几个月前一直无法使用,使用浏览器用代理后直接跳转到一个com.com的网站,不清楚是什么原因,但是使用curl确实是没有问题的curl -x socks5://127.0.0.1:1080 -I https://bt.byr.cn/login.php。前者返回的是302,后者是200。

后来找到了zYeoman的代码,也不清楚当时是怎么搜到的,可以用,但是HTML格式好像有变动,这个好解决,稍微程序动下就好了。

然后自己新建了个byr-spider库,方便自己使用吧,加了requirements.txt,加了代理,这个我可以在没有IPv6的情况下,通过代理一样下载种子,然后看有什么自己喜欢需要下载的,就把种子文件传到VPS自己下载就好了。

在家里的NAS机器上新增了定时任务,自己拉最新的种子文件。

1
@hourly cd ~/byr && pipenv run python byr.py > log.log 2>&1

起因

有一天查看OpenWrt设备状态的时候,突然发现一个熟悉的Mac地址,而且没有主机名。感觉心里不踏实,毕竟已经做了最初级的SSID隐藏,我脑袋里想到的第一反应就是之前有人来家里玩的时候,连了家里的WIFI,而且还使用了什么WIFI密码盒子之类的软件,这类软件拿到GPS定位或运营商定位,再结合扫描的SSID信息,可能已经把我的WIFI信息和密码传到服务器上面了。这样我就必须要做进一步的改善了,当然最简单的方法就是改密码,但是家里有其他人使用,改动比较费事;再想到的就是WIFI连接时的MAC白名单验证,但我想这个可能通过抓包也能好样的出来,不是太好。

解决思路

家里的路由器基本都是用的OpenWrt,应该能通防火墙规则来做点文章,思路是MAC地址白名单,可连LAN,无法使用WAN,放狗搜了下,找到一些文章。How to Block Device on OpenWRT Based on MAC Address

修改

其实就是在Network -> Firewall里的Zones中的Zone Lan => REJECT,然后再给需要使用Internet的设备增加相应规则。改动就是修改OpenWrt中的/etc/config/firewall

最后一点就是内网做好防护工作,OpenWrt的SSH禁用密码,只能使用密钥;uhttpd修改为SSL,且修改端口。s

NAS搭建

其实很早的时候就想搞一台NAS玩了,毕竟有很多想收藏的高清视频,还有一些自己私人资料,不方便存在云盘里面的。之前HP Gen8机器还是不错,扩展性也非常不错,只是感觉有点小贵,然后就是风扇噪音问题比较严重,查了下说是HP虚拟的一个Raid卡造成的问题,在*nix下面无法获取到硬盘的温度,导致风扇会以最大转速工作。当然查Gen8的资料是最近正式想整一台NAS后才开始。之前是用蛮早的一台上网本,好像是三星N2600之类的型号吧,装了CentOS,专门下载PT和存照片。后来空间不够用,就想起还是组一台NAS吧,正好家里有6台笔记本(Surface Pro 4, Thinkpad T400, Thinkpad E330, Thinkpad E420 x 2, Samung N2600),2.5寸硬盘有6个,本着不能浪费的原则,就开始整NAS了。

技术选型

19年春节前就一直想着这个事情,老天也是“给力”,春节期间还真是没有出过一个太阳,就宅在家里想着怎么组这个NAS。

FreeBSD/ZFS/Jail

FreeBSD很稳定,port也非常好用。之前工作的主力系统是Gentoo,所以对FreeBSD很有好感。但是ZFS一直没有正式接触过,于是把FreeBSD Handbook好好地看了下,确实ZFS很强大,可以忘掉Raid,忘掉分区,不同目录设置不能参数适应不同用途(加密,压缩等等),快照功能。还有Jail可用,用途有点类似Linux下的Docker,可以把程序关到Jail里运行,不影响主系统。

但是真正用起来感觉还有好多东西需要深入研究,但又没那么多时间。

  1. ZFS很强大,但同时也是内存大户,推荐是1T配1G内存,然后建议是配ECC内存(这个作者原意思是关心数据的话,不管是什么文件系统,都建议ECC)。现在内存不是太贵,但是要自己组NAS的话,ECC内存好像不好搞定。
  2. ZFS优化比较麻烦。在虚拟机里用ZFS和UFS测试NextCloud/MySQL时,发现ZFS明显慢很多。放狗搜了下,关于ZFS优化MySQL这方面,有蛮多参数需要配置的。
  3. ZFS快照功能暂时好像用不上,没这需要。但蛮喜欢加密和压缩功能的。
  4. Jail配置NAT网络比较麻烦,不如Docker来得方便。而且PF与iptables又不一样,还要查不少资料,烦。
  5. 担心FreeBSD的硬件兼容性,不能够让NAS达到最节能的状态。

所以最后还是放弃了FreeBSD。

OpenMediaVault(OMV)

之前本来确实是想用FreeBSD来组NAS的,后来@kurtyan推荐使用OMV。然后就是虚拟机里搞了一把,有几个槽点吧。

  1. OMV镜像安装不支持UEFI,这个锅应该让Debian 8背吧。。。然后就是OMV镜像安装时,好像要选整个硬盘,这个有点恶心,后来干脆自己先装好Debian 9然后再装OMV。
  2. 感觉Web管理页面上有太多我不想要,也用不着的功能,看着比较浪费。

感觉OMV比较适合新手,界面什么的还是比较友好的。后来一想这个web管理界面,FreeBSD的webadmin也可以管理啊,一查Linux也可以用webadmin。但最后还是没有用webadmin,自己从基层组算了。

Debian/LVM/Docker

OMV是基于Debian的,还不如自己搭建想要的功能呢。刚开始有考虑CentOS,但本人不喜欢它的图形安装界面,好像比Debian耗资源一些,就pass掉了。

既然是自己用的NAS就需要考虑下数据的安全问题,以防硬盘突然挂掉,开始考虑的是软raid,重要数据raid 1,视频数据raid 0,后来考虑再三还是放弃了软raid的方案,买的是4盘位机箱,比较少,还是用USB3.0外挂硬盘,定时备份重要数据,特别重要的文件就GnuPG加密后传网盘。还有一个就是使用LVM管理分区,这样可以最大限度地使用硬盘空间,后面系统配置的时候再详细谈下。其实ZFS对于这两点很适合,send/recv备份很方便,分区根本不需要管,把不同硬盘加到不同pool就OK了。

还有一个选Linux的原因应该就是Docker了。这样可以很大程序减少应用程序对系统的干扰,把能在Docker下面跑的都放进去,各自跑各自的。

所以我最后选择了Debian开干。

硬件配置

硬件配置最开始设想的是4个SATA3接口,2个网卡,PICe接口进行扩展,CPU性能无所谓。网上搜了不少,候选方案有下面几个:

  1. HP Gen8。2个数据网口,1个管理网口。这点对于服务器管理非常重要,丢在某个角落完全不用管,可直接iLO进行所有操作。有4个盘位,还有内置的USB、SD卡插口。就是有点小贵,还有风扇噪音。
  2. HP Gen10。2个数据网口,4个盘位,AMD CPU。感觉也OK,风扇不吵了,但是少了iLO,而且低配价格也很高。
  3. GA N3160主板。2个数据网口,4个盘位,PCI插槽。刚开始是冲是它去的,最后PCI插槽放弃了,而且价格比J3455贵100多大洋。
  4. ASRock J4105/J3455。1个数据网口,4个盘位,PCIe x1插槽。两块板CPU有差别,内存一个是DDR4,一个是DDR3。但是J3455支持16G,J4105只支持8G。最后选择了J3455,因为它便宜 :)

其实最早是蛮想2个网口链路聚合的,这样多处同时从NAS存取数据的时候,网络不会成为其瓶颈,后来想了想,估计同时多处使用NAS的情况最近几年都不会有,后面如果确实有需要,可以PCIe转双网卡就OK。家里网线是六类双屏蔽(那个硬啊,现在有点后悔用双屏蔽的了),交换机Linksys SLM2008,路由器Buffalo WZR-HP-AG300H,光纤入户100M,所以暂时屋内1G局域网应该够用了。

这里给自己上了一课吧,当时查ASRock官网J3455的时候,就注意到SATA的第3,4接口是ASMedia ASM1061出来的,跟我后来买的PCIe转SATA接口是一样的。好像最高是400MB/s,就是如果这几个接口上SSD的话,可能会影响期性能。而且PCIe 2.0 x1的最高速度是500MB/s,这块也成了影响其性能的要点了。

电源选择方面,可以选小的DC转24针这种,完全没有噪音,功耗也低,但是但是贵啊。。。。最后选了一个ENP 7025B 250W的Flex电源,有噪音,功耗也比DC高,但是但是便宜啊 :(

开始机箱自带的风扇是12cm,直接从电源取电,也就是说一直是全速状态。看了下J3455板是CPU和机箱风扇电源接口都是3线的,所以可以调节风扇转速,后来又购了1个12cm 1500rpm的3线风扇。可惜板子不支持4线的风扇。。。

嗯,后面再详细记录下系统方面的配置吧。

软件配置

Debian

Debian安装比较简单了,将网络安装的ISO写入到U盘,安装就OK了。其实完全可以用PXE来搞定,后面再记录下PXE吧。

LVM

LVM里面有几个名词,PV物理卷(可以是硬盘,分区,文件),VG卷组(由物理卷组成的组,有点类似ZFS的Pool),LV逻辑卷(类似普通分区,可格式化,挂载)。

PV操作

1
2
3
4
dd if=/dev/zero of=/dev/xxx bs=512 count=1
pvcreate /dev/xxx
pvs
pvremove /dev/xxx

VG操作

1
2
3
4
5
6
7
vgcreate vg-nas /dev/xxx /dev/xxx
vgextend vg-nas /dev/xxx
vgreduce vg-nas /dev/xxx
vgchange -a y/n vg-nas # activating/deactivating vg
vgremove vg-nas
vgrename vg-nas-old vg-nas-new
vgs

LV操作

1
2
3
4
5
6
lvcreate -L 1T -n lv-data vg-nas # linar
lvcreate -l 100%FREE -n lv-other vg-nas
lvs
lvextend -l +100%FREE -n vg-name lv-name # /dev/vg-name/lv-name
resize2fs /dev/vg-name/lv-name
lvreduce --resizefs -L 500G vg-name/lv-name

LVM调整挂载点容量大小就比较方便了,不明白的时候可以看RedHat的在线文档,写得很详细。

sshd

默认SSHD是密码登录的,可以用sshkey-gen生成密钥后,再用ssh-copy-id将公钥复制到远程主机。NAS我需要internet也能访问到,这样SSHD默认配置就不太合适了,需要改几个地方,其实打开/etc/ssh/sshd_config就一目了然,从配置字面上就能知道啥作用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Protocol 2

PermitRootLogin no
StrictModes yes
MaxAuthTries 6 #BAN IP的话可以用Fail2ban
MaxSessions 10

AuthorizedKeysCommand none
AuthorizedKeysCommandUser nobody

PasswordAuthentication no
PermitEmptyPasswords no

AllowGroups sshusers #将需要使用ssh的用户加入sshusers用户组

内网穿透

之前内网穿透使用的是ssh端口转发,NAS用远程端口转发-R 2222:127.0.0.1:22 VPS(VPS的2222端口转发到NAS的22端口),然后在终端机使用本地端口转发-L 4444:127.0.0.1:2222 VPS(终端机4444端口转发到VPS的2222端口),这样在终端机ssh -p 4444 localhost就可以访问NAS了,很方便不是?后来好像有时候网络不稳定,掉线后NAS不会自己进行端口转发,然后就找到了现在用的frpopenwrt frp

其中frp VPS Server端的配置如下。其实程序自带的说明文件里说得很详细,认真看下就可以配得很好了。

/etc/frp/frps.ini
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
[common]
bind_addr = 0.0.0.0
bind_port = 7000

bind_udp_port = 7001

tcp_bind_port = 7000

dashboard_addr = 127.0.0.1
dashboard_port = 7500

dashboard_user = user
dashboard_pwd = pwd

log_file = /var/log/frps.log

log_info = info
log_max_days = 3

privilege_token = privilege_token

max_pool_count = 5
max_ports_per_client = 0

authentication_timeout = 900
tcp_mux = true
frps.service
1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=frps daemon
After=network.target
Wants=network.target

[Service]
Type=simple
User=nobody
Group=nogroup
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini

[Install]
WantedBy=multi-user.targe

其中NAS的frp配置如下。OpenWrt的话,直接使用screen,sleep 30 && screen -dmS frpc frpc -c /etc/frp/frpc.ini,加到/etc/rc.local

frpc.ini
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
[common]
server_addr = VPS_IP
server_port = 7000

log_file = /var/log/frpc.log
log_level = info
log_max_days = 3

privilege_token = privilege_token

admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin_user
admin_passwd = admin_pwd

pool_count = 5
tcp_mux = true
user = nas
log_fail_exit = true
protocol = tcp

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 2222
frpc.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=FRP Client Daemon
After=network.target
Wants=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.ini
Restart=always
RestartSec=20s
User=nobody

[Install]
WantedBy=multi-user.target

下载工具

NAS的话必须要下载片子咯,BT/PT使用的是transmission,普通其它下载用的是aria2。使用transmission需要下载几个包apt install transmission-{daemon, remote{,-cli}},平时使用的话我在bash里加了下载几个alias。

1
2
3
4
alias lcp='rsync -avhW --no-compress --progress'
alias t-remote='transmission-remote -n user:password localhost'
alias t-remote='transmission-remote-cli -c user:password@localhost:9091'
alias a-d='aria2c -x 10 -j 1 --http-user user --http-passwd passwd'

NextCloud

其中架NAS最重要的一点就是网盘,放一些家庭照片和视频,常跟老婆説,等以后崽结婚的时候,把小时候的照片拿出来看,哈哈哈。

之前家里用的是FreeBSD下面架设的,这次换成Debian,那肯定就用Docker来跑这些咯,以后迁移起来也是非常快的,打包带走。。。

docker-compose.yml
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
version: '3.7'

services:
db:
image: mariadb
restart: always
volumes:
- ./db:/var/lib/mysql
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro
environment:
- MYSQL_ROOT_PASSWORD=pwd1
- MYSQL_PASSWORD=pwd2
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
nextcloud:
image: nextcloud:15-fpm-alpine
depends_on:
- db
volumes:
- ./html:/var/www/html
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro
restart: always
nginx:
build: ./nginx
depends_on:
- db
- nextcloud
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./html:/var/www/html
restart: always

其中./nginx/default.conf可以直接参考官网文档,然后还有一点就是nginx和nextcloud的docker image中用户名ID不一致,这样共享的目录./html就不能同时使用。需要调整下,nginx的image很小,可以直接从它下手,nginx的Dockerfile如下。

nginx
1
2
3
4
FROM nginx:alpine

RUN sed -i 's/:100:/:82:/g' /etc/passwd; \
sed -i 's/:101:/:82:/g' /etc/passwd /etc/group

SAMBA

家里其他设备访问NAS估计用得最多的就是SAMBA了吧,直接在/etc/samba/smb.conf最后加上一些配置就OK了,也懒得再认真配置了,只要能访问到就OK。

smb.conf
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
[public]
comment = public
path = /data/BTDownloads
browsable = yes
create mask = 0660
directory mask = 0771
writable = no
guest ok = yes
[videos]
comment = public
path = /mnt/videos
browsable = yes
create mask = 0660
directory mask = 0771
writable = no
guest ok = yes
[win7]
comment = public
path = /mnt/cdrom
browsable = no
create mask = 0660
directory mask = 0771
writable = no
guest ok = yes
[personal]
comment = personal
path = /mnt/nas/personal
browsable = no
create mask = 0660
directory mask = 0771
writable = yes
guest ok = no

NFS

家里还有一些其他设备,比如路由器(OpenWrt)和Raspberry Pi,它们可以很方便的使用NFS,这样NAS提供NFS服务就可以了。修改/etc/exports

1
/mnt/nas/Tools  *(no_subtree_check,ro,all_squash,insecure,async)

其它

硬盘多了,功率也大,安装包hdparm可以设置硬盘standby的时间,据説可以延长使用时间,减小功耗,前提是不要设置得太小,硬盘磁头一停一启动地可能更耗硬盘。修改/etc/hdparm.conf

/etc/hdparm.conf
1
2
3
/dev/sdb {
spindown_time = 250
}

安装hddtemp sensors来读取温度值。

BIOS设置

关闭非必要功能,节能设置等

WOL,需要开启PCIE power on

tips

  1. NAS下载了很多片片,要传到手机上看,之前都是iTunes通过WIFI读取SAMBA共享文件,再传到VLC,慢得1B,还得再多开台电脑。后来了解到可以直接用curl传,方便了很多。curl -F "filename='filename'" -F "files[]=@filepath" http://VLC_IP/upload.json,本来想写个shell脚本自动上传目录下的所有文件的,但是没有找到好的方法,干脆用python吧,直接造成shell脚本运行。 :(不会直接使用Python上传到VLC,POST传的东西跟平时有点不一样。。。

  2. 下载Dropbox文件,Bash脚本Dropbox Uploader,在VPS上面下载Dropbox的照片,然后再传回来。

  3. 下载OneDrive文件,用windows里面的客户端可能是最简单的,但没有在NAS里面安装虚拟机,也不想平时再开台机器。后来想了一条路,Chrome下载OneDrive的照片目录,zip文件,然后打开Chrome开发工具,直接将刚才那个请求Copy as cURL,这样可以直接在NAS下载照片了。

  4. Picasaweb。long long ago,那时候Google还没有被封,传照片就到Picasa,哈哈,里面好多年轻的照片啊。这里需要设置下,在Google Drive里面设置下,将Google Photos照片导入到Google Drive,然后使用rclone将里面的照片复制到本地。rclone copy remote:Google\ Photos /mnt/photos -P

  5. 照片存了这么多份,肯定有重复的。最开始写了脚本进行查重,思路就是找出所有图片文件,对md5码,发现效率不高,后来放狗搜发现一个好工具fdepesfind . ! -empty -type f -size +500k -size -50M -exec md5sum {} + | sort > ~/allfiles,然后再对allfiles文件用unique就能找出重复文件了。fdupes -r -S -1 Data Documents nextcloud Pictures Work > ~/allfiles,这个也是找出所有重复文件,还可以直接使用fdupes快速删除重复文件。

后期优化

家庭网络换成1G带宽,扩展链路聚合。路由器换成了Buffalo AG300H,交换机换成了Cisio SLM2008,这样家里千兆OK了,但是发现丫我的无线是100M的WAN和LAN,电视机是100M口,一口老血吐出来。电视机的话,使用Xbox One S看得了,1000M口。无线路由器有空再换个好点的吧。开始装修的时候就是考虑NAS链路聚合的,毕竟多设备同时访问NAS的话,不链路聚合的话可能会成为瓶颈。到时候NAS换了PCIe的双网卡,硬盘只要4口,全换成4T盘,这样也OK的。

PXE

PXE的方案我使用的是DHCP Server在OpenWrt,然后tftp server在NAS。

DHCP配置/etc/config/dhcp

1
2
3
4
config boot 'linux'
option filename 'pxelinux.0'
option serveraddress '192.168.1.222'
option servername 'Liangwu PXE SERVER'

tftp-hpa配置。这里我在NAS上面直接将tftp跑在docker里面的。下载Debian netboot,并解压到res目录。

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.7'

services:
tftp-hpa:
image: jumanjiman/tftp-hpa
restart: always
ports:
- "69:69/udp"
volumes:
- ./res/debian-installer/amd64/boot-screens/syslinux.cfg:/tftpboot/pxelinux.cfg/default:ro
- ./res/debian-installer:/tftpboot/debian-installer:ro
- ./res/windows:/tftpboot/windows:ro

修改./res/debian-installer/amd64/boot-screens/syslinux.cfg,增加下面内容支持windows安装。制作PE镜像,加入网卡驱动,这些在Microsoft文档可以找到。

1
2
3
4
5
6
7
label 9
menu label Install Windows 7
KERNEL memdisk
INITRD windows/winpe_7.iso
APPEND iso raw
prompt 0
timeout 0

安装

家里好几台电脑,现在有Surface Pro 4的Windows 10, Thinkpad T400的Windows 7和XUbuntu 18.04.1,老婆老的三星上网本NP110C的FreeBSD 11和CentOS 7, 公司电脑Thinkpad E550的Gentoo和XUbuntu 18.04.1。还有台破的Thinkpad E330,想着周末没啥事,就装个省点事的版本吧,本来是装好了Ubuntu,但是感觉不好,重新装个ArchLinux吧。

EFI/GPT

其实以前也装过ArchLinux,是在T400上面。现在在E330上面,有一些地方不一样,主要是GPT和EFI的问题,所以就记录一下吧。

首先分区的时候,用的是fdisk,用GPT。大概分区方式如下

物理分区文件系统挂载点
/dev/sda1EFI System/boot
/dev/sda2Ext4/
/dev/sda3Ext4/data
/dev/sda4Swap

EFI进行分区,mkfs.fat -F32 /dev/sda1。安装相应软件efibootmgr,不知道有没有用,反正我没有试过不安装有没有影响。

GRUB安装

EFI的GRUB有点不一样,以前只需要grub-install /dev/sda就好了,但是现在不行了。现在GRUB安装如下,其中/boot是之前分区的EFI分区,Ubuntu就是另外一分单独的分区,它是单独挂载在/boot/EFIbootloader-id是EFI启动界面显示的ID。

1
2
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /etc/grub/grub.cfg

wireless

笔记本肯定要用无线上网嘛,可是我家SSID是隐藏的,就用了自带的netctl控制。在/etc/netctl/examples下面有好多示例文档,用wireless-wpa就好了。

1
2
cp /etc/netctl/examples /etc/netctl/liangwu
netctl start liangwu

这样在安装的时候就可以上网了。但是安装的新系统,如果不安装netctl或其它网络相关软件的话,新系统还是无法上网。其中netctl还有一些依赖需要安装。dialog用于wifi-menu使用,wpa_supplicant用于密钥使用。

其它

hostnamectl设置主机名

timedatectl设置时区等等

Desktop Environment

驱动需要根据自己环境变动。

1
2
3
4
5
6
pacman -S xorg-server xf86-video-intel xf86-video-nouveau xfce4 xfce4-goodies lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings networkmanager nm-connection-editor network-manager-applet papirus-icon-theme alsa-utils pavucontrol pasystray pulseaudio pulseaudio-alsa dnsutils fcitx fcitx-configtool fcitx-gtk3 fcitx-qt5
vim /etc/lightdm/lightdm.conf
vim /etc/lightdm/lightdm--gtk-greeter.conf
vim /etc/systemd/logind.conf
systemctl enable lightdm.service
systemctl enable NetworkManager

MyBatis FAQ

用MyBatis有点点时间了,也碰到一些问题。放狗搜了之后,发现官方github上的FAQ已经有一些东西,是可以先看看的。

批量插入

之前用MyBatis进行批量插入时,一直都在MyBatis的Mapper.xml里面用foreach拼成一个比较长的SQL,这个时候MySQL和Oracle的拼法还有点不一样。MySQL可以直接用INSERT INTO (id) VALUES (1),(2),(3)这种形式,而Oracle不行,Oracle需要且UNION ALLFROM DUAL或直接BEGIN ... END

后来了解看renren security的源码时候,发现有使用MyBatis Plus,在MyBatis Plus里面的ServiceImpl<M extends BaseMapper<T>, T>类中有个saveBatch方法,看日志发现他不是用接长SQL方法实现的,跟Groovy里面的batch操作是一样的。后来搜索后发现MyBatis其实有个Executor本身就带批量操作功能。。。

1
2
3
4
5
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, true)) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userList.forEach(userMapper::insert);
sqlSession.flushStatements();
}

其实我感觉应该是自己对JDBC不熟悉的原因吧,等有时间了去专门学习学习。这个我碰到一个坑,就是Spring boot进行JUnit测试操作的时候,在方法上加上@Transactional,事务回滚。可能是Spring boot test里面本身对事务就是这样处理的?这个有时间也是需要去解决的。// TODO

基本类型(String, Integer等)的列表映射

如果一个查询结果只有一列,这样返回的列表很容易进行映射。但是当一个对象里面有两个属性,一个ID,一个基本类型列表时,在之前查MyBatis文档的时候没有发现,后来在网上搜到了,映射的时候还是用collection,但是里面的只填一个result column。刚好在MyBatis FAQ里面也看到了。搬过来吧。

比如SQL搜索结果是

idstr
1A
2B

Java对象

1
2
3
4
5
@Data
public class SomeBean {
private Integer id;
private List<String> strings;
}

XML映射如下

1
2
3
4
5
6
<resultMap id="" type="SomeBean">
<id column="id" property="id" />
<collection property="strings" ofType="string" javaType="list">
<result column="str" />
</collection>
</resultMap>

另外官方其它的FAQ还有变量使用时#{}${}的区别:$在生成ps的时候,就已经替换了,存在安全问题,#在生成ps的时候,是用?代替,运行的时候再进行值代入。

SQL LIKE,这个有好多实现的方法,比如String自带%,使用bind,直接在SQL里面使用字符拼接。

插入时将自动生成的key返回,在insert映射的时候加入useGeneratedKeys="true" keyProperty="id"

mapper方法传参使用变量名,这个直接使用注解@Param,或直接包装成对象或Map,或mybatis.configuration.use-actual-param-name= true

SSH端口转发

ssh真的是很好用啊,传文件,本地/远程/动态端口转发,远程,X转发等等。X转发当时搞过一次,服务器没有安装X环境,无图形界面,要安装Oracle,当时搞了半天,用静默安装的形式安装好了一台。后来发现可以用SSH X转发,图形界面在我的机器上,实际安装程序在服务器上,好好玩啊。

嗯嗯,总结下在实际工作中使用最多的就是端口转发了。

本地端口转发

本地端口转发的命令是:

1
ssh -L [bind_address:]port:host:hostport gate-way-server

ssh文档中对本地端口转发的说明:

Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side. This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address. Whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to host port hostport from the remote machine. Port forwardings can also be specified in the configuration file. IPv6 addresses can be specified with an alternative syntax:
[bind_address/]port/host/hostport or by enclosing the address in square brackets. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the GatewayPorts setting. However, an explicit bind_address may be used to bind the connection to a specific address. The bind_address of ‘’localhost’’ indicates that the listening port be bound for local use only, while an empty address or ‘*’ indicates that the port should be available from all interfaces.

我的理解就是:本地开一个端口(port),访问这个端口的效果和gate-way-server访问host:hostport的效果一样。这样我体会到的最大的用处就是可以访问受限制(gate-way-server)的内网端口。

比如我在外面出差,想访问公司内部server-A3389端口,server-B3306端口,server-C1521端口。但是这些端口肯定是不会对外开放的,我访问server-outer服务器对外暴露的22端口。于是我通过下面的命令就可以解决我的需求。

1
ssh -L 3389:server-A:3389 -L 3306:server-B:3306 -L 1521:server-C:1521 -L 2222:localhost:22 server-outer -fNnC

这样,我就可以直接访问本地的3389, 3306, 1521, 2222端口来达到访问server-A:3389, server-B:3306, server-C:1521, server-outer:22端口的目的了。可能表述的不是太清楚,网上有几张图说明得很直白。

Local port forwarding

远程端口转发

1
ssh -R [bind_address:]port:host:hostport remote-server

Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side. This works by allocating a socket to listen to port on the remote side, and whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to host port hostport from the local machine.

Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 addresses can be specified by enclosing the address in square braces or using an alternative syntax:
[bind_address/]host/port/hostport.

By default, the listening socket on the server will be bound to the loopback interface only. This may be overridden by specifying a bind_address. An empty bind_address, or the address ‘*’, indicates that the remote socket should listen on all interfaces. Specifying a remote bind_address will only succeed if the server’s GatewayPorts option is enabled (see sshd_config(5)).

If the port argument is ‘0’, the listen port will be dynamically allocated on the server and reported to the client at run time.

大概意思就是在remote-server上面绑定一个port,访问这个port的效果,就和本机现在访问host:hostport效果一样。

这种情况我也有使用,比如某处有两台电脑,pc-1能访问外网,pc-2不能访问外网,但pc-1pc-2能互通。这时我在公司想访问pc-1pc-23389端口,这个时候我就可以在pc-1机器上执行:

1
ssh -R 13389:localhost:3389 -R 23389:pc-2:3389 remote-server

其中remote-server为公司的一台电脑,互联网可以访问。这样我直接访问remote-server1338923389端口就可以直接连接到pc-1:3389pc-2:3389端口了,十分好用啊。

哈哈,是不是相当于一个简单的内网穿透功能了。

Remote port forwarding

动态端口转发

1
ssh -D port remote-server

Specifies a local ‘’dynamic’’ application-level port forwarding. This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address. Whenever a connection is made to this port, the connection is forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. Currently the SOCKS4 and SOCKS5 protocols are supported, and ssh will act as a SOCKS server. Only root can forward privileged ports. Dynamic port forwardings can also be specified in the configuration file.

IPv6 addresses can be specified with an alternative syntax:
[bind_address/]port or by enclosing the address in square brackets. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the GatewayPorts setting. However, an explicit bind_address may be used to bind the connection to a specific address. The bind_address of ‘’localhost’’ indicates that the listening port be bound for local use only, while an empty address or ‘*’ indicates that the port should be available from all interfaces.

通过本地的port访问互联网,相当于通过remote-server来访问互联网。

嗯嗯,以前用来当梯子用,后来墙升级,很不稳定,后来好像大家都没用它来当梯子了。当然墙应该不是破解了ssh,应该是只流量识别吧。

Dynmaic port forwarding

参考文章

SSH 安全性和配置入门

实战 SSH 端口转发

What’s ssh port forwarding and what’s the difference between ssh local and remote port forwarding [duplicate]

npm包安装问题

之前在家基本都是用Surface Pro 4所以基本也就没有安装Nodejs。今天晚上正好有点时间,就把老的Thinkpad T400拿出来,写点东西总结东东吧,要不一直不总结,埋头处理工作,感觉一直没有进步。

感觉还有好多东西没有写,SSH Local/Remote Proxykubernetes环境搭建(not hard way)gitlab及runner在k8s里面搭建MyBatis学习,这些东西下周的时间全要写下来吧,要不全都要忘记了。

npm在天朝第一步应该是用cnpm吧,这一点我还是蛮喜欢alibaba的,在国内搞了一些方便开发者的镜像。

1
$ npm install -g cnpm --registry=https://registry.npm.taobao.org

这里-g就全局安装需要root权限,所以需要改下npm包管理的配置。

  1. 创建包安装目录
1
mkdir ~/.npm-packages
  1. 配置npm,将全局安装的包安装到上一步创建的目录
1
echo "prefix=${HOME}/.npm-packages" >> ~/.npmrc
  1. 配置环境PATH去正确目录查找npm包和man文件。将下面内容加入到~/.bashrc~/.zshrc
1
2
3
4
5
NPM_PACKAGES="${HOME}/.npm-packages"
PATH="$NPM_PACKAGES/bin:$PATH"

unset MANPATH
export MANPATH="$NPM_PACKAGES/share/man:$(manpath)"
  1. 更新环境
1
source ~/.zshrc

这个时候再去全局安装npm包的话,就不需要root权限,直接将包安装到~/.npm-packages目录下面了。

参考文章

Install npm packages globally without sudo on macOS and Linux

Resolving EACCES permissions errors when installing packages globally