0%

数据是最重要的电子资产,但是在持有及维护的过程,需要消耗不少的成本。于是,不得不去探索廉价又安全的数据存储方案。

背景

在云时代,不论是G家,还是AWS等这些大厂,都提供了非常可靠好用的服务。但是,整套使用下来,即便是按量计费,也是一个比较大的花销。

而非大厂的服务提供商,往往能给出价格实惠的虚拟主机,独立主机。但便宜的机器多多少少会有不稳定的问题,而且数据安全上,往往我们无法去信任小商家。

因此,我们是否能使用大厂的存储服务,使用其他商家的主机呢?

经过初步的检索,大致得出这样一套方案去尝试。

数据存储:Amazon EFS。提供可伸缩的数据存储。

接入EFS的代理机:Lightsail 。使用Lightsail而不选 EC2,也是基于成本考虑。EC2更优,5刀/月。

主力机器:独服 4C 32G。25刀/月。

需要数据卷持久化的数据都存储在EFS中,这样基本机器损坏,被搬走等各种原因,数据在AWS上还是可靠的。

安装 amazon-efs-utils

非 Amazon Linux,需要安装 amazon-efs-utils,才能使用efs。

1
2
3
4
5
6
yum -y install git
git clone https://github.com/aws/efs-utils
yum -y install make rpm-build
cd efs-utils
make rpm
yum -y install ./build/amazon-efs-utils*rpm

创建 EFS

image-20201229235242942

通过管理面板创建EFS

image-20201229235334335

在Network面板中,能看到EFS的IP address,创建完成。

配置 VPC,建立对等链接

因为使用的不是EC2,而是Lightsail。默认情况下是无法直接连接Amazon的服务的。需要建立对等连接。

在Lightsail的面板里面,操作建立对等连接。

image-20201229235638796

勾选 “启用 VPC 对等” 即可

在 VPC 面板中,查看对等连接是否建立成功。

image-20201229235829671

配置 EFS 入站规则

image-20201230000452803

新增一个安全组,单独给EFS访问使用。

出站规则默认全放行。

入站规则,因为Lightsail的内网IP与EC2的不同,需要单独放行。添加 172.26.0.0/16 所有流量放行。

到此所有准备工作完成,可以挂载数据卷了。

Lightsail 挂载数据卷

1
2
3
mkdir -p /mnt/efs
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport 172.31.22.169:/ /mnt/efs
# 如果没有返回错误,通过mount查看挂载情况,成功。

image-20201230000918942

配置 Nginx 代理 NFS TCP端口

1
2
3
4
5
6
7
8
9
10
stream {
upstream backend {
server 172.31.22.169:2049;
}
server {
listen 2049;
# 有需要的话,这里也可以做安全策略
proxy_pass backend;
}
}

在Lightsail的防火墙规则,放开2049端口。仅放开目标机器的ip。

image-20201230003254277

外部系统挂载数据卷

因为我的外部系统是Debian 10,也是需要安装 amazon-efs-utils。

1
2
3
4
5
apt-get -y install git binutils
git clone https://github.com/aws/efs-utils
cd efs-utils
./build-deb.sh
apt-get -y install ./build/amazon-efs-utils*deb

然后执行mount进行挂载, 只是将IP改成Lightsail的公网IP

1
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport $PUBLICIP:/ /mnt/efs

设置开机启动自动挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建目录
mkdir -p /aws-efs

# Edit /etc/fstab 追加
$PUBLICIP:/ /aws-efs nfs defaults,_netdev 0 0

# 可以用 mount 指令检测下是否写对
mount -fav
# mount -fav
/ : ignored
mount.nfs: timeout set for Tue Dec 29 17:36:13 2020
mount.nfs: trying text-based options 'vers=4.2,addr=$PUBLICIP,clientaddr=$PUBLICIP'
/aws-efs : successfully mounted

性能测试

通过dd指令读写2G数据进行对比

1
2
3
4
5
# write
time dd if=/dev/zero of=/mnt/efs/2g-dd-data bs=1M count=2048 conv=fsync

# read
time dd if=/mnt/efs/2g-dd-data of=/dev/null bs=1M count=2048 conv=fsync
  1. Lightsail 本地 IO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # time dd if=/dev/zero of=/root/2g-dd-data bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB) copied, 33.3782 s, 64.3 MB/s

    real 0m33.386s
    user 0m0.011s
    sys 0m1.049s

    # time dd if=/mnt/efs/2g-dd-data of=/dev/null bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB) copied, 32.5695 s, 65.9 MB/s

    real 0m32.578s
    user 0m0.000s
    sys 0m0.944s
  2. 本机EFS IO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # time dd if=/dev/zero of=/mnt/efs/2g-dd-data bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB) copied, 19.2469 s, 112 MB/s

    real 0m19.254s
    user 0m0.000s
    sys 0m1.078s

    # time dd if=/mnt/efs/2g-dd-data of=/dev/null bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB) copied, 19.3482 s, 111 MB/s

    real 0m19.352s
    user 0m0.005s
    sys 0m0.760s
  3. 远程主机 EFS IO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # time dd if=/dev/zero of=/mnt/efs/2g-dd-data bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB, 2.0 GiB) copied, 67.4047 s, 31.9 MB/s

    real 1m10.265s
    user 0m0.005s
    sys 0m1.225s

    # time dd if=/mnt/efs/2g-dd-data of=/dev/null bs=1M count=2048 conv=fsync
    2048+0 records in
    2048+0 records out
    2147483648 bytes (2.1 GB, 2.0 GiB) copied, 0.266611 s, 8.1 GB/s

    real 0m0.274s
    user 0m0.000s
    sys 0m0.266s

结语

整个流程下来,可以预期的是,在外网访问EFS是可行的。

然后从测试数据来看,即便在一个Lightsail性能非常差的机器上面,网络IO也会比本地IO性能优越很多。

但是在远程机器IO的读写测试就变得不准确了,而且写入速度来说,确实会有非常大的损失。这里的性能损失有多方面的原因。

即便我的另一个机房的主机,与AWS Lightsail的主机ping值是低于10ms的网络情况下。网络的读写也不高。这里nginx的反向代理参数没配置对,机器的性能等等也是原因。

从测试结果来看,即便选用低配的 EC2,这里的性能也会有可观的增长。那为什么选用LIghtsail,那还是为了省钱。。。

其实原本这套方案是想接入G家的GCP的Cloud Filestore的。但是G家的数据卷,最低配1T存储定量,不是按可用来算。月费用惊人。。。

最后, 测试数据文件赶紧删掉。占用是要花钱的 XD

image-20201230011852390


下期预告:Amazon-EFS提供外部访问数据卷(二)—— 配置传输过程数据加密

前置声明:虽然Debian默认禁用root登录,但是作为运维人员,还是有必要使用root的,下面所有指令,也是基于root账号操作的,因此并没有sudo。

安装CRI-O

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
# 设置环境变量,系统,目标版本,我这里是Debian 10,使用当前最新的1.20的CRI-O
OS=Debian_10
VERSION=1.20
# 添加源,这里官网的例子中,如果使用的是小版本号,源的url路径有点问题,需要注意。例如 1.18的1.18.4 版本,设置 VERSION=1.18:1.18.4, 但是对应的 URL 应该是 .../1.18:/1.18.4/Debian_10,如果使用的是别的版本,注意修改这里
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

# 导入key
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -

# 更新
apt-get update

# Debian 10 稳定源里面,libseccomp2 的版本比较旧, 是2.3的,这里需要自己手动升级一下,因此这里是否会引入不稳定的情况,很难预估。毕竟官网里面写的版本,还是使用的Debian_Testing,稳定版还没有。
# 下载最新的 libseccomp2 进行安装
wget http://ftp.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_amd64.deb
dpkg -i libseccomp2_2.5.1-1_amd64.deb

# 安装 cri-o
apt-get install cri-o cri-o-runc

# 启动cri-o
service crio start

安装k8s

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
# disable swap
swapoff -a
# 编辑 /etc/fstab 去掉 swap 的配置

# 配置启动加载模块
modprope br_netfilter
# 在 /etc/modules 追加 br_netfilter,以便重启后自动加载 br_netfilter

# 配置
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF
sysctl --system

# install k8s,k8s的版本需要与crio的版本对应,crio是1.20的,k8s的也是1.20
apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

vim /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main

apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

配置k8s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用kubeadm初始化
PUBLICIP=xxx.xxx.xxx.xxx
kubeadm init --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/16 --apiserver-advertise-address=$PUBLICIP --cri-socket=/var/run/crio/crio.sock

# 配置
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 安装网络插件
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 确认下是否配置成功
kubectl get nodes -n kube-system
kubectl get pods -n kube-system

# 配置 dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.1.0/aio/deploy/recommended.yaml

后面就不用再说了,k8s成功跑起来。

image-20201227171247731

结语

其实之前一直使用的CentOS 7,k8s的容器引擎也是默认的Docker。为什么会切换到Debian与CRI-O。

首先是,CentOS 7的内核比较旧,其实这点我还是能接受,但是问题出在最近更新的一个独立服务器上面,它家的系统的CentOS7,内核是定制过的,不支持overlayfs。而且如果自己更换内核,无法正常启动…

这是被迫要改系统,然后就切换到Debian 10了。

而换CRI-O,这个感觉是趋势,早点换看看踩踩坑,Dockershim毕竟是要废弃的,而且软件的依赖也没处理好,经常不小心升级后会出现各种奇怪的问题。

这些更换目前都不是性能/安全等原因上的考虑,只是被迫换系统的场景下,干脆直接换到CRI-O,提前做做准备。后续使用过程有什么坑,也会一一记录。

1. 创建client

1
2
3
4
5
6
7
8
9
func CreateFirestoreClient(ctx context.Context) *firestore.Client {
projectID := "xxxx"
client, err := firestore.NewClient(ctx, projectID, option.WithCredentialsJSON([]byte(CredentialsJSON)))
if err != nil {
log.Println(err)
return nil
}
return client
}

2. 添加数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func AddData() {
// create client
ctx := context.Background();
client := CreateFirestoreClient(ctx)
defer client.Close()

// prepare data
// pbObj is target data
var jsonbuf []byte
jsonbuf, _ = json.Marshal(pbObj)

var bsonMap bson.M
d := json.NewDecoder(bytes.NewBuffer(jsonbuf))
d.UseNumber()
if err := d.Decode(bsonMap); err != nil {
log.Println(err)
return
}

// add data to collection
c := client.Collection("path")
c.Add(ctx, bsonMap)
}

3. 查询数据

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
func Query() {
// create client
ctx := context.Background();
client := CreateFirestoreClient(ctx)
defer client.Close()

// query data
query := client.Collection("path").Where("data_id", "==", "1")
iter := query.Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Failed to iterate: %v", err)
}

// convert data
data := doc.Data()
// marshal to jsonbuf
jsonbuf, _ := json.Marshal(data)
// pbObj target object
jsonpb.Unmarshal(bytes.NewReader(jsonbuf), &pbObj)
}
}

编译不同平台时,或追加平台时,需要在当前工程追加。但直接通过 flutter create . 会失败。

原因是flutter create创建的工程目录调整了,package name与当前目录名字不一样。

因此这时候需要指定project-name参数

1
flutter create --project-name myproj --org com.example .

image-20200804033913418

遇到类似问题时

一般是因为node的版本太新导致。回退一下node的版本即可。


使用brew方式回退版本

1
2
3
brew unlink node
brew install node@12
brew link --overwrite --force node@12
完成

然后重新

1
2
hexo clean
hexo g -d