0%

一、系统配置与安装docker (云边节点都操作执行)

1、禁用开机自启动防火墙

1
2
systemctl stop firewalld
systemctl disable firewalld

2、关闭selinux

1
2
sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0

3、关闭swap

1
2
sed -ri 's/.*swap.*/#&/' /etc/fstab
swapoff -a

4、在各节点添加hosts与githubusercontent解析

1
2
3
4
5
6
7
cat >> /etc/hosts << EOF
192.168.2.201 master1
192.168.2.202 master2
192.168.2.203 master3
192.168.2.205 node1
199.232.68.133 raw.githubusercontent.com
EOF

5、安装docker

1
2
3
4
5
6
7
8
9
10
yum install -y wget 
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-18.06.1.ce-3.el7
systemctl enable docker && systemctl start docker

cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF

二、安装cloud节点

1、安装kubeadm、kubelet、kubectl

1
2
3
4
5
6
7
8
9
10
11
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0 ipvsadm

2、将桥接的IPv4流量传递到iptables的链

1
2
3
4
5
6
7
8
9
10
11
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
vm.swappiness=0
EOF

sysctl --system

modprobe br_netfilter
sysctl -p /etc/sysctl.d/k8s.conf

加载ipvs相关内核模块
如果重新开机,需要重新加载(可以写在 /etc/rc.local 中开机自动加载)

1
2
3
4
5
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack_ipv4

查看是否加载成功

1
lsmod | grep ip_vs

3、部署kubernetes Master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
systemctl enable kubelet
kubeadm init --apiserver-advertise-address=192.168.2.201 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.18.0 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12
apiserver-advertise-address地址对应修改为master ip

执行成功后需要执行如下命令
To start using your cluster, you need to run the following as a regular user:

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

并复制保存最后的join命令
kubeadm join 192.168.2.201:6443 --token uzm4y0.t1x8tuurgtnybsht \
--discovery-token-ca-cert-hash sha256:feaf0da3bf0d6ff6cdaeff51b4f9145a138061831a4350815312b2d21ea5ab07

4、部署网络插件CNI

1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

kubectl get pods -n kube-system等待所有pod都为running状态

5、下载配置golang环境

1
2
wget https://golang.google.cn/dl/go1.15.3.linux-amd64.tar.gz
tar -zxvf go1.15.3.linux-amd64.tar.gz -C /usr/local

配置golang环境
vi /etc/profile

1
2
3
4
5
6
7
8
# golang env
export GOROOT=/usr/local/go
export GOPATH=/data/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

source /etc/profile
mkdir -p /data/gopath && cd /data/gopath
mkdir -p src pkg bin

6、下载KubeEdge源码

1
git clone https://github.com/kubeedge/kubeedge $GOPATH/src/github.com/kubeedge/kubeedge

#git指令安装: yum install -y git
#下载失败可以使用

1
git clone https://gitee.com/mirrors/KubeEdge.git $GOPATH/src/github.com/kubeedge/kubeedge

切换分支

1
cd $GOPATH/src/github.com/kubeedge/kubeedge && git checkout -b release-1.5 remotes/origin/release-1.5

7、编译kubeadm

1
2
cd $GOPATH/src/github.com/kubeedge/kubeedge
make all WHAT=keadm

编译后的二进制文件在./_output/local/bin下,单独编译cloudcore与edgecore的方式如下:

1
make all WHAT=cloudcore && make all WHAT=edgecore

8、提前下载安装包kubeedge-v1.5.0-linux-amd64.tar.gz

1
2
mkdir /etc/kubeedge
cd /etc/kubeedge && wget https://ghproxy.com/https://github.com/kubeedge/kubeedge/releases/download/v1.5.0/kubeedge-v1.5.0-linux-amd64.tar.gz

arm版下载链接:

1
wget https://ghproxy.com/https://github.com/kubeedge/kubeedge/releases/download/v1.5.0/kubeedge-v1.5.0-linux-arm64.tar.gz

9、创建cloud节点

1
2
3
cd $GOPATH/src/github.com/kubeedge/kubeedge/_output/local/bin
./keadm init --advertise-address="192.168.2.201"
--advertise-address参数为cloud节点ip

10、添加系统服务

1
2
3
4
5
cp $GOPATH/src/github.com/kubeedge/kubeedge/build/tools/cloudcore.service /etc/systemd/system/cloudcore.service 
cp $GOPATH/src/github.com/kubeedge/kubeedge/_output/local/bin/cloudcore /etc/kubeedge/cloudcore
systemctl daemon-reload
systemctl enable cloudcore
systemctl start cloudcore

三、安装edge节点

1、edge端mosquitto安装

添加EPEL软件库
下载mosquitto

1
2
yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install mosquitto

2、提前下载安装包kubeedge-v1.5.0-linux-amd64.tar.gz

1
2
mkdir /etc/kubeedge
cd /etc/kubeedge && wget https://ghproxy.com/https://github.com/kubeedge/kubeedge/releases/download/v1.5.0/kubeedge-v1.5.0-linux-amd64.tar.gz

3、创建edge节点

在cloud节点执行获取token

1
2
3
cd $GOPATH/src/github.com/kubeedge/kubeedge/_output/local/bin/
./keadm gettoken
示例:627a75341b826dc5187e5ca16a7ad2a8b5ebf25b2351e2bab4f5a7db2ad2a8ec.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDkxNTU0NTV9.2fgxRXTlXO6GnecGmpJD2jhIX-QyVZy84IPHQQ1ewFc

将cloud端的keadm拷贝到edge端加入集群

1
scp keadm root@192.168.2.203:/root

执行

1
./keadm join --cloudcore-ipport=<cloud所在的ip>:10000 --edgenode-name=<edge名字(自己取)> --kubeedge-version=1.5.0 --token=<前面获取的token值>

示例:

1
./keadm join --cloudcore-ipport=192.168.2.200:10000 --edgenode-name=node1 --kubeedge-version=1.5.0 --token=3a0a30db83552cf96e195027aa84a34602ee41db9913dcde09ad2235d295b49e.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTMwMTgxMDZ9.UaGcyIeTvzQ7vnJEhhZuimG_u_OXtKkitfZSctH74nU

四、安装后残留问题解决

1、开启使用kubectl logs:

主节点执行

1
2
3
4
5
6
7
cp $GOPATH/src/github.com/kubeedge/kubeedge/build/tools/certgen.sh /etc/kubeedge/ 
cd /etc/kubeedge/
/etc/kubeedge/certgen.sh stream

export CLOUDCOREIPS="192.168.2.201"

iptables -t nat -A OUTPUT -p tcp --dport 10350 -j DNAT --to $CLOUDCOREIPS:10003

修改如下文件

1
2
3
4
5
6
7
8
9
10
11
vi /etc/kubeedge/config/cloudcore.yaml 
cloudStream:
enable: true
streamPort: 10003
tlsStreamCAFile: /etc/kubeedge/ca/streamCA.crt
tlsStreamCertFile: /etc/kubeedge/certs/stream.crt
tlsStreamPrivateKeyFile: /etc/kubeedge/certs/stream.key
tlsTunnelCAFile: /etc/kubeedge/ca/rootCA.crt
tlsTunnelCertFile: /etc/kubeedge/certs/server.crt
tlsTunnelPrivateKeyFile: /etc/kubeedge/certs/server.key
tunnelPort: 10004

重启CloudCore服务

边缘节点执行

修改如下文件,server项改为主节点ip

1
2
3
4
5
6
7
8
9
10
11
vi /etc/kubeedge/config/edgecore.yaml

edgeStream:
enable: true
handshakeTimeout: 30
readDeadline: 15
server: 192.168.0.139:10004
tlsTunnelCAFile: /etc/kubeedge/ca/rootCA.crt
tlsTunnelCertFile: /etc/kubeedge/certs/server.crt
tlsTunnelPrivateKeyFile: /etc/kubeedge/certs/server.key
writeDeadline: 15

vi /etc/kubeedge/edgecore.service加入

1
Environment="CHECK_EDGECORE_ENVIRONMENT=false"

重启edgecore服务

K8S的学习之旅-安装minikube

安装 docker desktop

取消settings中的use the WSL 2 based engine

  • docker desktop 默认已经安装了kubectl

安装minikube

启动集群

1
minikube start
启动失败,提示
1
2
3
4
5
6
7
* Microsoft Windows 10 Education 10.0.18363 Build 18363 上的 minikube v1.16.0
* Unable to pick a default driver. Here is what was considered, in preference order:
- vmware: Not installed: exec: "docker-machine-driver-vmware": executable file not found in %PATH%
- docker: Not installed: exec: "docker": executable file not found in %PATH%
- hyperv: Not healthy: Hyper-V requires Administrator privileges
- podman: Not installed: exec: "podman": executable file not found in %PATH%
- virtualbox: Not installed: unable to find VBoxManage in $PATH

原因是没有默认的驱动,这里选择的是docker做为driver。


minikube支持的driver有五种

  • vmware
  • docker
  • hyperv
  • podman
  • virtualbox

vmware、virtualbox相信装过虚拟机的同学都知道这两个虚拟机应用程序。

docker是比虚拟机还轻量的容器技术。

hyper-v是Windows系统的本地虚拟机管理程序,可在程序与功能->启用或关闭 Windows功能中查看到。

podman则是一个无守护进程的容器引擎,用于在Linux上开发、运行或管理OCI容器。


  • 查看docker是否开启
1
docker -v
  • 成功启动docker后,再次启动集群
1
minikube start
  • 自动下载driver,拉去image

  • 完成后有默认的命名空间(获取命名空间:kubectl get ns)

1
2
3
4
5
6
C:\Users\Caisi>kubectl get ns
NAME STATUS AGE
default Active 30m
kube-node-lease Active 30m
kube-public Active 30m
kube-system Active 30m
  • 查看集群运行情况
1
2
3
4
5
6
7
8
9
C:\Users\Caisi>kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-74ff55c5b-jcpt7 1/1 Running 0 48m
kube-system etcd-minikube 1/1 Running 0 48m
kube-system kube-apiserver-minikube 1/1 Running 0 48m
kube-system kube-controller-manager-minikube 1/1 Running 0 48m
kube-system kube-proxy-mj5vg 1/1 Running 0 48m
kube-system kube-scheduler-minikube 1/1 Running 0 48m
kube-system storage-provisioner 1/1 Running 1 48m

都启动成功

  • 启动minikube dashboard控制台
1
2
3
4
5
6
C:\Users\Caisi>minikube dashboard
* 正在开启 dashboard ...
* 正在验证 dashboard 运行情况 ...
* Launching proxy ...
* 正在验证 proxy 运行状况 ...
* Opening http://127.0.0.1:53756/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...

部署示例应用(这里是个坑,看看就可以了)

  • 创建应用
1
2
C:\Users\Caisi>kubectl create deployment hello-minikube --image=k8s.gcr.io/echoserver:1.4
deployment.apps/hello-minikube created
  • 公开应用端口(在集群中)
1
2
C:\Users\Caisi>kubectl expose deployment hello-minikube --type=NodePort --port=8080
service/hello-minikube exposed
  • 查看服务
1
2
3
C:\Users\Caisi>kubectl get services hello-minikube
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-minikube NodePort 10.111.191.44 <none> 8080:32207/TCP 20s
  • 查看pod容器启动情况
1
2
3
C:\Users\Caisi>kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-minikube-6ddfcc9757-mbtkk 0/1 ImagePullBackOff 0 7m54s

这里显示镜像拉取失败了


  • 使用命令查看pod运行详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Users\Caisi>kubectl describe pods hello-minikube-6ddfcc9757-mbtkk
Name: hello-minikube-6ddfcc9757-mbtkk
Namespace: default
Priority: 0
Node: minikube/192.168.49.2
···
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8m14s default-scheduler Successfully assigned default/hello-minikube-6ddfcc9757-mbtkk to minikube
Normal Pulling 5m51s (x4 over 8m13s) kubelet Pulling image "k8s.gcr.io/echoserver:1.4"
Warning Failed 5m36s (x4 over 7m58s) kubelet Failed to pull image "k8s.gcr.io/echoserver:1.4": rpc error: code = Unknown desc = Error response from daemon: Get https://k8s.gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
Warning Failed 5m36s (x4 over 7m58s) kubelet Error: ErrImagePull
Warning Failed 5m9s (x7 over 7m57s) kubelet Error: ImagePullBackOff
Normal BackOff 3m2s (x15 over 7m57s) kubelet Back-off pulling image "k8s.gcr.io/echoserver:1.4"
  • 也可以通过minikube bashboard查看

这里出现问题的原因是k8s.gcr.io是国外Google的网站,国内无法访问,也就拉取不了镜像了。
##真正成功的部署示例应用
解决方法是使用国内的镜像源如:aliyuncs
删除刚刚的deployment和service

1
2
3
4
5
C:\Users\Caisi>kubectl delete deployment hello-minikube
deployment.apps "hello-minikube" deleted

C:\Users\Caisi>kubectl delete service hello-minikube
service "hello-minikube" deleted
  • 重新创建应用
1
2
3
4
5
6
C:\Users\Caisi>kubectl create deployment hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.10
deployment.apps/hello-minikube created

C:\Users\Caisi>kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-minikube-8947b65df-g78l7 1/1 Running 0 88s

可以看到pod已经运行成功了

  • 重新设置集群公开端口
1
2
3
4
5
6
C:\Users\Caisi>kubectl expose deployment hello-minikube --type=NodePort --port=8080
service/hello-minikube exposed

C:\Users\Caisi>kubectl get services hello-minikube
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-minikube NodePort 10.102.25.156 <none> 8080:32470/TCP 8s
  • 设置转发端口让本地可以访问
1
2
3
4
5
C:\Users\Caisi>kubectl port-forward service/hello-minikube 7080:8080
Forwarding from 127.0.0.1:7080 -> 8080
Forwarding from [::1]:7080 -> 8080
Handling connection for 7080
Handling connection for 7080

使用浏览器访问http://localhost:7080/可以看到服务的请求信息

管理集群

  • 暂停(不影响已部署的应用程序)
1
minikube pause
  • 停止
1
minikube stop
  • 查看内置的已安装的服务目录
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
C:\Users\Caisi>minikube addons list
|-----------------------------|----------|--------------|
| ADDON NAME | PROFILE | STATUS |
|-----------------------------|----------|--------------|
| ambassador | minikube | disabled |
| csi-hostpath-driver | minikube | disabled |
| dashboard | minikube | enabled ✅ |
| default-storageclass | minikube | enabled ✅ |
| efk | minikube | disabled |
| freshpod | minikube | disabled |
| gcp-auth | minikube | disabled |
| gvisor | minikube | disabled |
| helm-tiller | minikube | disabled |
| ingress | minikube | disabled |
| ingress-dns | minikube | disabled |
| istio | minikube | disabled |
| istio-provisioner | minikube | disabled |
| kubevirt | minikube | disabled |
| logviewer | minikube | disabled |
| metallb | minikube | disabled |
| metrics-server | minikube | disabled |
| nvidia-driver-installer | minikube | disabled |
| nvidia-gpu-device-plugin | minikube | disabled |
| olm | minikube | disabled |
| pod-security-policy | minikube | disabled |
| registry | minikube | disabled |
| registry-aliases | minikube | disabled |
| registry-creds | minikube | disabled |
| storage-provisioner | minikube | enabled ✅ |
| storage-provisioner-gluster | minikube | disabled |
| volumesnapshots | minikube | disabled |
|-----------------------------|----------|--------------|
  • 删除所有集群
1
minikube delete --all

总结

  1. 学会了一些markdown的编辑技巧
  2. 最近弄的好多都需要国外的网站,如这次的k8s.gcr.io,还有昨天在GitHub上面找的几个super-resolution都是国外的项目,有现成的模型,但是下载很麻烦,2M多下了半天,还一直下载失败。所以,在学习新的技术前,要了解一下需不需要国外的资源,以及国内有没有相应的资源可供选择。
  3. 这次只是简单的环境搭建,开了个头,接下来的才是开始。
  4. 简单的安装了docker、minikube,以及运行了示例应用。

K8s 或 K3s 集群中持久化存储方案选型_k3s storageclass-CSDN博客

Excerpt

文章浏览阅读3.1k次。存储架构1 三个概念: pv , pvc ,storageclasspv - 持久化卷, 支持本地存储和网络存储, 例如hostpath,ceph rbd, nfs等,只支持两个属性, capacity和accessModes。其中capacity只支持size的定义,不支持iops等参数的设定,accessModes有三种,ReadWriteOnce(被单个node读写), ReadOnlyMany(被多个nodes读), ReadWriteMany(被多个nodes读写)pvc - 对pv或_k3s storageclass


存储架构

1 三个概念: pv , pvc ,storageclass

  • pv - 持久化卷, 支持本地存储和网络存储, 例如hostpath,ceph rbd, nfs等,只支持两个属性, capacity和accessModes。其中capacity只支持size的定义,不支持iops等参数的设定,accessModes有三种,ReadWriteOnce(被单个node读写), ReadOnlyMany(被多个nodes读), ReadWriteMany(被多个nodes读写)
  • pvc - 对pv或者storageclass资源的请求, pvc 对 pv 类比于pod 对不同的cpu, mem的请求。
  • storageclass-另外一种提供存储资源的方式, 提供更多的层级选型, 如iops等参数。 但是具体的参数与提供方是绑定的。 如aws和gce它们提供的storageclass的参数可选项是有不同的。

2 三种PV的访问模式

  • ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载。
  • ReadOnlyMany:可以以只读的方式被多个Pod挂载。
  • ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。

不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是NFS。在PVC绑定PV时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。

3 三个重声明策略(reclaim policy)

  • Retain – 手动重新使用
  • Recycle – 基本的数据擦除 (“rm -rf /thevolume/*”)
  • Delete – 相关联的后端存储卷删除, 后端存储比如AWS EBS, GCE PD, Azure Disk, or OpenStack Cinder

需要特别注意的是只有本地盘和nfs支持数据盘Recycle 擦除回收, AWS EBS, GCE PD, Azure Disk, and Cinder 存储卷支持Delete策略

4 四个阶段(volumn phase)

一个存储卷会处于下面几个阶段中的一个阶段:

  • Available –资源可用, 还没有被声明绑定
  • Bound – 被声明绑定
  • Released – 绑定的声明被删除了,但是还没有被集群重声明
  • Failed – 自动回收失败

5 四个PV选择器

在PVC中绑定一个PV,可以根据下面几种条件组合选择

  • Access Modes, 按照访问模式选择pv
  • Resources, 按照资源属性选择, 比如说请求存储大小为8个G的pv
  • Selector, 按照pv的label选择
  • Class, 根据StorageClass的class名称选择, 通过annotation指定了Storage Class的名字, 来绑定特定类型的后端存储

6 五个可移植性建议

  • 把你的 pvc,和 其它一系列配置放一起, 比如说deployment,configmap
  • 不要把你的pv放在其它配置里, 因为用户可能没有权限创建pv
  • 初始化pvc 模版的时候, 提供一个storageclass
  • 在你的工具软件中,watch那些没有bound的pvc,并呈现给用户
  • 集群启动的时候启用DefaultStorageClass, 但是不要指定某一类特定的class, 因为不同provisioner的class,参数很难一致

7 六个生命周期: Provisioning, Binding, Using, Releasing, Reclaiming, Recycling

k8s对pv和pvc之间的交互的生命周期进行管理。

  • provisioning- 配置阶段, 分为static, dynamic两种方式。静态的方式是创建一系列的pv,然后pvc从pv中请求。 动态的方式是基于storageclass的。
  • Binding - 绑定阶段, pvc根据请求的条件筛选并绑定对应的pv。 一定pvc绑定pv后, 就会排斥其它绑定,即其它pvc无法再绑定同一个pv,即使这个pv设定的access mode允许多个node读写。 此外 ,pvc 如何匹配不到相应条件的pv, 那么就会显示unbound状态, 直到匹配为止。 需要注意的是,pvc请求100Gi大小的存储,即使用户创建了很多50Gi大小的存储, 也是无法被匹配的。
  • Using- 使用阶段, pods 挂载存储, 即在pod的template文件中定义volumn使用某个pvc。
  • Releasing - 释放阶段, 当pvc对象被删除后, 就处于释放阶段。 在这个阶段, 使用的pv还不能被其它的pvc请求。 之前数据可能还会留存下来, 取决于用户在pv中设定的policy, 见persistentVolumeReclaimPolicy。
  • Reclaiming - 重声明阶段。 到这个阶段, 会告诉cluster如何处理释放的pv。 数据可能被保留(需要手工清除), 回收和删除。动态分配的存储总是会被删除掉的。
  • Recycling - 回收阶段。回收阶段会执行基本的递归删除(取决于volumn plugins的支持),把pv上的数据删除掉, 以使pv可以被新的pvc请求。 用户也可以自定义一个 recycler pod , 对数据进行删除。

8 九个PV Plugins

pv是以plugin的形式来提供支持的, 考虑到私有云的使用场景, 排除掉azure, aws,gce等公有云厂商绑定的plugin, 有9个插件值得关注。这些plugin所支持的accessmode是不同的。 分别是

存储方案

共享存储为分布式系统非常重要的一部分,存储一般要求稳定、可用、性能、可靠。

1 用户角度

存储就是一块盘或者一个目录,用户不关心盘或者目录如何实现,用户要求非常“简单”,就是稳定,性能好。为了能够提供稳定可靠的存储产品,各个厂家推出了各种各样的存储技术和概念。

2 存储介质

存储介质分为机械硬盘和固态硬盘(SSD)。机械硬盘泛指采用磁头寻址的磁盘设备,包括SATA硬盘和SAS硬盘。由于采用磁头寻址,机械硬盘性能一般,随机IOPS一般在200左右,顺序带宽在150MB/s左右。固态硬盘是指采用Flash/DRAM芯片+控制器组成的设备,根据协议的不同,又分为SATA SSD,SAS SSD,PCIe SSD和NVMe SSD。

3 产品定义

存储分为本地存储(DAS),网络存储(NAS),存储局域网(SAN)和软件定义存储(SDS)四大类

  • DAS就是本地盘,直接插到服务器上
  • NAS是指提供NFS协议的NAS设备,通常采用磁盘阵列+协议网关的方式
  • SAN跟NAS类似,提供SCSI/iSCSI协议,后端是磁盘阵列
  • SDS是一种泛指,包括分布式NAS(并行文件系统),ServerSAN等

4 应用场景

存储分为文件存储(Posix/MPI),块存储(iSCSI/Qemu)和对象存储(S3[minio]/Swift)三大类
Kubernetes是如何给存储定义和分类呢?Kubernetes中跟存储相关的概念有PersistentVolume (PV)和PersistentVolumeClaim(PVC),PV又分为静态PV和动态PV。静态PV方式如下:

动态PV需要引入StorageClass的概念,使用方式如下:

社区列举出PersistentVolume的in-tree Plugin,如下图所示。从图中可以看到,Kubernetes通过访问模式给存储分为三大类,RWO/ROX/RWX。这种分类将原有的存储概念混淆,其中包含存储协议,存储开源产品,存储商业产品,公有云存储产品等等。

如何将Kubernetes中的分类和熟知的存储概念对应起来呢?本文选择将其和应用场景进行类比。

1.块存储通常只支持RWO,比如AWSElasticBlockStore,AzureDisk,有些产品能做到支持ROX,比如GCEPersistentDisk,RBD,ScaleIO等

2.文件存储(分布式文件系统)支持RWO/ROX/RWX三种模式,比如CephFS,GlusterFS和AzureFile
3.对象存储不需要PV/PVC来做资源抽象,应用可以直接访问和使用 api

这里不得不吐槽Kubernetes社区前期对存储层的抽象,一个字——乱,把开源项目和商业项目都纳入进来。现在社区已经意识到问题并设计了统一的存储接口层——Flexvolume/CSI。目前来看,CSI将会是Kubernetes的主流,做了完整的存储抽象层

多种多样的应用场景

介绍完存储概念之后,选择哪种存储仍然悬而未决。这个时候,请问自己一个问题,业务是什么类型?选择合适的存储,一定要清楚自己的业务对存储的需求。本文整理了使用容器存储的场景及其特点。

  • 配置
    无论集群配置信息还是应用配置信息,其特点是并发访问,也就是前边提到的ROX/RWX,在不同集群或者不同节点,都能够访问同样的配置文件,分布式文件存储是最优选择。

  • 日志
    在容器场景中,日志是很重要的一部分内容,其特点是高吞吐,有可能会产生大量小文件。如果有日志分析场景,还会有大量并发读操作。分布式文件存储是最优选择。

  • 应用(数据库/消息队列/大数据)
    Kafka,MySQL,Cassandra,PostgreSQL,ElasticSearch,HDFS等应用,本身具备了存储数据的能力,对底层存储的要求就是高IOPS,低延迟。底层存储最好有数据冗余机制,上层应用就可以避免复杂的故障和恢复处理。以HDFS为例,当某个datanode节点掉线后,原有逻辑中,会选择启动新的datanode,触发恢复逻辑,完成数据副本补全,这段时间会比较长,而且对业务影响也比较大。如果底层存储有副本机制,HDFS集群就可以设置为单副本,datanode节点掉线后,启动新的datanode,挂载原有的pv,集群恢复正常,对业务的影响缩短为秒级。高性能分布式文件存储和高性能分布式块存储是最优选择。

  • 备份
    应用数据的备份或者数据库的备份,其特点是高吞吐,数据量大,低成本。文件存储和对象存储最优。
    综合应用场景,高性能文件存储是最优选择,形形色色的存储产品

形形色色的存储产品

市面上的存储产品种类繁多,但是对于容器场景,主要集中在4种方案:分布式文件存储,分布式块存储,Local-Disk和传统NAS。

分布式块存储包括开源社区的Ceph,Sheepdog,商业产品中EMC的Scale IO,Vmware的vSAN等。分布式块存储不适合容器场景,关键问题是缺失RWX的特性。

分布式文件存储包括开源社区的Glusterfs,Cephfs,Lustre,Moosefs,Lizardfs,商业产品中EMC的isilon,IBM的GPFS等。分布式文件存储适合容器场景,但是性能问题比较突出,主要集中在GlusterFS,CephFS,MooseFS/LizardFS。
这里简单对比下开源项目的优缺点:

 Local-Disk方案有明显的缺点,尤其是针对数据库,大数据类的应用。节点故障后,数据的恢复时间长,对业务影响范围广。
传统NAS也是一种文件存储,但是协议网关(机头)是性能瓶颈,传统NAS已经跟不上时代发展的潮流。

多样的评估策略

存储的核心需求是稳定,可靠,可用。无论是开源的存储项目还是商业的存储产品,评估方法具有普适性,介绍常见的评估项和评估方法.

  • 数据可靠性
    数据可靠性是指数据不丢失的概率。通常情况下,存储产品会给出几个9的数据可靠性,或者给出最多允许故障盘/节点个数。评估方式就是暴力拔盘,比如说存储提供3副本策略,拔任意2块盘,只要数据不损坏,说明可靠性没问题。存储采用不同的数据冗余策略,提供的可靠性是不一样的。

  • 数据可用性
    数据可用性和数据可靠性很容易被混淆,可用性指的是数据是否在线。比如存储集群断电,这段时间数据是不在线,但是数据没有丢失,集群恢复正常后,数据可以正常访问。评估可用性的主要方式是拔服务器电源,再有查看存储的部署组件是否有单点故障的可能。

  • 数据一致性
    数据一致性是最难评估的一项,因为大部分场景用户不知道程序写了哪些数据,写到了哪里。该如何评估数据一致性呢?普通的测试工具可以采用fio开启crc校验选项,最好的测试工具就是数据库。如果发生了数据不一致的情况,数据库要么起不来,要么表数据不对。具体的测试用例还要细细斟酌。

  • 存储性能
    存储的性能测试很有讲究,块存储和文件存储的侧重点也不一样。

         

        块存储

        fio/iozone是两个典型的测试工具,重点测试IOPS,延迟和带宽。以fio为例,测试命令如下:

1
fio -filename=/dev/sda -iodepth=${iodepth} -direct=1 -bs=${bs} -size=100% --rw=${iotype} -thread -time_based -runtime=600 -ioengine=${ioengine} -group_reporting -name=fioTest

       关注几个主要参数:iodepth,bs,rw和ioengine。

       测试IOPS,iodepth=32/64/128,bs=4k/8k,rw=randread/randwrite,ioengine=libaio

       测试延迟,iodepth=1,bs=4k/8k,rw=randread/randwrite,ioengine=sync

       测试带宽,iodepth=32/64/128,bs=512k/1m,rw=read/write,ioengine=libaio

      文件存储

       fio/vdbench/mdtest是测试文件系统常用的工具,fio/vdbench用来评估IOPS,延迟和带宽,           mdtest评估文件系统元数据性能。以fio和mdtest为例,测试命令如下:

1
fio -filename=/mnt/wubo/fio.test -iodepth=1 -direct=1 -bs=${bs} -size=500G --rw=${iotype} -numjobs=${numjobs} -time_based -runtime=600 -ioengine=sync -group_reporting -name=fioTest

     与块存储的测试参数有一个很大区别,就是ioengine都是用的sync,用numjobs替换iodepth。

     测试IOPS,bs=4k/8k,rw=randread/randwrite,numjobs=32/64

     测试延迟,bs=4k/8k,rw=randread/randwrite,numjobs=1

     测试带宽,bs=512k/1m,rw=read/write,numjobs=32/64

     mdtest是专门针对文件系统元数据性能的测试工具,主要测试指标是creation和stat,需要采用.       mpirun并发测试:

1
mpirun --allow-run-as -root -mca btl_openib_allow_ib 1 -host wubonode1:${slots},wubonode1:${slots},wubonode2:${slots} -np ${num_procs} mdtest -C -T -d /mnt/wubo/mdtest -i 1 -I ${files_per_dir} -z 2 -b 8-L -F -r -u

   存储性能测试不仅仅测试集群正常场景下的指标,还要包含其他场景:

  1. 存储容量在70%以上或者文件数量上亿的性能指标

  2. 节点/磁盘故障后的性能指标

  3. 扩容过程时的性能指标.

  

  • 容器存储功能
    除了存储的核心功能(高可靠/高可用/高性能),对于容器存储,还需要几个额外的功能保证生产环境的稳定可用。

  • Flexvolume/CSI接口的支持,动态/静态PV的支持

  • 存储配额。对于Kubernetes的管理员来说,存储的配额是必须的,否则存储的使用空间会处于不可控状态

  • 服务质量(QoS)。如果没有QoS,存储管理员只能期望存储提供其他监控指标,以保证在集群超负荷时,找出罪魁祸首

选择产品本质:

Kubernetes持久化存储方案的重点在存储和容器支持上。因此首要考虑存储的核心功能和容器的场景支持。综合本文所述,将选择项按优先级列举:

1、存储的三大核心,高可靠,高可用和高性能

2、业务场景,选择分布式文件存储

3、扩展性,存储能横向扩展,应对业务增长需求

4、可运维性,存储的运维难度不亚于存储的开发,选择运维便捷存储产品

5、成本

Q&A 环节

Q:你好,我们公司采用GlusterFS存储,挂载三块盘,现在遇到高并发写小文件(4KB)吞吐量上不去(5MB/S),请问有什么比较好的监控工具或方法么?谢谢!

A:GlusterFS本身对小文件就很不友好,GlusterFS是针对备份场景设计的,不建议用在小文件场景,如果可以的话,要么程序做优化进行小文件合并,要么选用高性能的分布式文件存储,建议看看Lustre或者YRCloudFile。

Q:你好,目前开源在用Rook部署Ceph,Ceph对于块设备存储性能如何?可以提升吗?未来?

A:我们最近也在关注Rook项目,Rook的理念是很好的,但是现在Rook就是Ceph的封装,把Ceph跑到容器中,复用Kubernetes的监控平台。而Ceph的运维复杂度很高,以目前的做法,项目想要做好,难度会非常大。

Q:我看您推荐分布式文件存储,文件系统能满足数据库应用的需求吗?块存储会不会好一些?

A:首先,我推荐的是高性能分布式文件系统。数据库一般对延迟都比较敏感,普通的万兆网络+HDD肯定不行,需要采用SSD,一般能将延迟稳定在毫秒以内,通常能够满足要求。如果对延迟有特别要求,可以采用NVMe + RoCE的方案,即使在大压力下,延迟也能稳定在300微秒以内。

Q:请问为什么说块存储不支持RWX?RWX就是指多个节点同时挂载同一块块设备并同时读写吗?很多FC存储都可以做到。

A:传统的SAN要支持RWX,需要ALUA机制,而且这是块级别的多读写,如果要再加上文件系统,是没办法做到的,这需要分布式文件系统来做文件元数据信息同步。

Q:请问现在的Kubernetes环境下,海量小文件RWX场景,有什么相对比较好的开源分布式存储解决方案么?

A:开源的分布式文件存储项目中,没有能解决海量小文件的,我在文中已经将主流开源文件系统都分析了一遍,在设计之初,都是针对备份场景或者HPC领域。

Q:请问,为什么说Ceph性能不好,有依据吗?

A:直接用数据说话,我们用NVMe + Ceph + Bluestore测试出来的,延迟在毫秒级以上,而且很不稳定,我们用YRCloudFile + NVMe + RoCE,延迟能50微秒左右,差了几十倍。

Q:Lustre没接触过,性能好吗,和Ceph有对比过吗?

A:网上有很多Lustre的性能指标,在同样的配置下,性能绝对要比Ceph好,不过Lustre全部都是内核态的,容器场景没办法用,而且按照部署以及运维难度非常大。Lustre在超算用的比较广泛。

Q:Lustre只能靠本地磁盘阵列来保证数据冗余么?

A:Lustre本身不提供冗余机制,都是靠本地阵列的,不过EC好像已经在开发计划中了。

Q:(对于小公司)如果不选用商业存储,那么推荐哪款开源实现作为生产存储(可靠,高性能)。我们之前试了NFS发现速度不稳定?

A:国内还是有很多创业公司,也不贵的。存储不像其他项目,存储经不起折腾,一定要用稳定可靠的,Ceph/GlusterFS做了这么久,大家在采购的时候,还是会依托于某家商业公司来做,自己生产环境用开源项目,风险太大了。

Q:GPFS用来做Kubernetes的PV,性能怎么样?

A:用GPFS的话,性能还是有一定保障的,不过GPFS跟Lustre一样,都是带着阵列一起卖的,很贵

感谢大神 这里只是记录一下 自己测试一下 

如何评估Kubernetes持久化存储方案

任务说明:公司一个绿色版的软件,为安装部署是需要很多的环境依赖,如 DevExpress、.net4.5、WinRAR等,客户提出安装复杂,并且有漏装后无法启动等情况,现将绿色版转安装版,并将依赖环境集成进去。

注:博主发现网上的教程大多是只讲了利用软件的引导部分实现简单的打包,少有对依赖环境的集成进行讲解,所以写下这一篇内容。 本人用的是汉化版,请使用英文版的自行对号入座即可。

1.1 InstallShield的安装

  InstallShield 2010下载地址  点击这里 ,安装方法网上有很多就不赘述了。

1.2 绿色版安装包的前期准备

如果你需要打包的绿色版软件比较小(1G以内),或者文件结构比较简单,请忽视此段,由于我所要打包的软件体积比较大,最重要的是文件结构比较复杂,内部文件夹嵌套较多,且例如图片,xml等小文件较多,这样 InstallShield软件在打包导入的时候会非常慢,我尝试导入了10多个小时也没有结束就放弃了,所以在这里我建议对文件结构复杂的软件进行压缩处理,压缩方式请转

RAR打包为自解压exe文件教程,得到一个单独的 EXE文件(自解压格式)后进行接下来的操作。

2.1创建新的 Windows Installer | InstallScript MSI Projcet

这种类型的工程既有打包向导,又可以写一点脚本实现自定义操作,比较适合使用。

2.2 打包向导主界面

这里是InstallShield提供的向导主界面,我们是通过点击选择下方的动作条进行操作。

2.3应用程序信息

公司信息,应用程序名称等等,这里没什么好说的。

2.4 安装要求

这里要说一下,大家可能会把这里理解为是安装依赖,但是这里与其说是依赖不如说是安装的前提或要求,这里勾选的环境 会在你双击setup后立即开始检测,并且在不满足条件时,只会对你做相应的提醒(如:请安装Adobe reader6!)后直接结束安装并退出,并没有引导用户安装的选项。明显与我们想将缺少的依赖环境集成引导加以安装的需求不相符,但是考虑到有些人会有这样的需求,也在接下来简单讲一下。

2.4.1 系统安装要求的勾选

对于InstallShield软件已经列出的选项直接勾选就可以,不再赘述。

2.4.2 自定义系统安装要求

对于InstallShield没有列出的我们可以通过左侧 创建一个自定义软件条件 来自行添加,如下图,你可以选择不同的满足条件(如 注册表某一项的值为XXX或 系统的某一路径下存在某一文件等等),这里的选项可以满足自定义几乎所有的安装要求。

2.5安装体系结构

在这里可以新建多个Feature,每一个Feature代表一类安装软件,具体以例子说明,见下面第二张图。

这中类型的图片大家一定很熟悉,这是大多数软件点击自定义安装后出现的界面, 以下图为例,My sql数据库,MuseMail这两个选项就对应两个Feature,也就是上一幅图中的defaultFeature和New_Feature2这两个,然后属于musemail类里面的多个软件就被添加到MuseMail的Feature下,对于MuseMail->初始化工具,则要创建初始化工具的Feature,以此类推。

2.6应用程序文件

在此界面为每一个Feature添加文件。若有多个文件可选择添加文件夹。 这些Feature将是用户自定义安装时选择的对象。

2.7 应用程序快捷方式

在这里添加在上一部分添加的文件中,每一个在你安装后的系统可能用到的文件的快捷方式,建议只添加必须的。右侧可以选择快捷方式出现的位置。

2.8应用程序注册表

在这里可以选择应用程序想要写入注册表的信息,右键添加即可,比较简单。

2.9安装本地化

这一部分用来选择安装语言

2.10构建安装

选择生成安装包。讲到这里比较简单的安装包已经可以打包好了,已经可以点击生成了,选择对应的生成类型即可。

选择安装设计器界面—>应用程序数据—>可再分发,在这里可以选择系统所需的环境,并且在环境不满足的情况下弹出下载安装的提醒。

InstallShield软件已经集成了部分依赖环境,可以勾选添加,并且可以在部署前下载,这样可以添加到安装包中,免除用户在安装过程中下载过程。但是InstallShield中不包含的怎么办呢,接下来来讲自定义依赖环境。

InstallShield提供的依赖环境是以 .prq文件的方式提供的,目录是    X:\..\InstallShield\2010\SetupPrerequisites,在这里有很多.prq文件,自定义不存在的依赖环境就是在网上下载.prq文件,或自己创建

常用prq文件地址

如果下载不到要如何创建呢 下面是创建的例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<?``xml version="1.0" encoding="UTF-8"?>

<``SetupPrereq``>

<``conditions``>

<``condition Type="2" Comparison="2" Path="HKEY_LOCAL_MACHINE\SOFTWARE\test" FileName="test" ReturnValue="1"></``condition``>

</``conditions``>

<``operatingsystemconditions``>

<``operatingsystemcondition MajorVersion="5" MinorVersion="1" PlatformId="2" CSDVersion="" Bits="1" ProductType="1" ServicePackMajorMin="2"></``operatingsystemcondition``>

<``operatingsystemcondition MajorVersion="5" MinorVersion="2" PlatformId="2" CSDVersion="" ProductType="2|3"></``operatingsystemcondition``>

<``operatingsystemcondition MajorVersion="5" MinorVersion="2" PlatformId="2" CSDVersion="" Bits="2" ProductType="1"></``operatingsystemcondition``>

<``operatingsystemcondition MajorVersion="6" MinorVersion="0" PlatformId="2" CSDVersion=""></``operatingsystemcondition``>

<``operatingsystemcondition MajorVersion="6" MinorVersion="0" PlatformId="2" CSDVersion="" ProductType="2|3"></``operatingsystemcondition``>

<``operatingsystemcondition MajorVersion="6" MinorVersion="0" PlatformId="2" CSDVersion="" ProductType="1"></``operatingsystemcondition``>

</``operatingsystemconditions``>

<``files``>

<``file LocalFile="<ISProductFolder>\SetupPrerequisites\test\test 3.5\test3.5.exe" URL="http://download.test.com/download/test3.5.exe" CheckSum="D481CDA2625D9DD2731A00F482484D86" FileSize="0,242743296"></``file``>

</``files``>

<``execute file="Helper.exe" cmdline="/p dotnetfx35.exe /l 1033 /v "/q /norestart"" cmdlinesilent="/p dotnetfx35.exe /l 1033 /v "/q /norestart"" returncodetoreboot="1641,3010" requiresmsiengine="1"></``execute``>

<``properties Id="{074EE22F-2485-4FED-83D1-AAC36C3D9ED0}" Description="This prerequisite installs the .NET Framework 3.5 Service Pack 1 full package." AltPrqURL="http://saturn.installshield.com/is/prerequisites/microsoft .net framework 3.5 sp1.prq"></``properties``>

<``behavior Reboot="2"></``behavior``>

</``SetupPrereq``>

<condition :

     该选项是说明当注册表 HKEY_LOCAL_MACHINE\SOFTWARE\test 路径的 指定项 test 的值为1 则是说明此环境符合要求,若不为1 则提醒安装指定程序。

<file LocalFile :

    用来说明下载的文件存储文位置:将该prq文件拷贝至X:\..\InstallShield\2010\SetupPrerequisites 后重启InstallShield软件后你会发现在X:\..\InstallShield\2010\SetupPrerequisites 文件夹下出现新的文件夹目录\test\test 3.5目录,将想要安装的 test3.5.exe 软件拷贝至该目录下,后再次重启软件(其实也不知道要不要重启,只是觉得应该要重启的)然后就可以在本部分图一中找到并且勾选了。

微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

以往我们开发应用程序都是单体型(可以看作是一个怪兽👿),虽然开发和部署比较方便,但后期随着业务的不断增加,开发迭代和性能瓶颈等问题,将会困扰开发团队,微服务就是解决此问题的有效手段,市面上有很多的微服务框架,比如最著名的两个 Dubbo 和 Spring Cloud,我们该如何选择呢?

公司近期打算向 Java 微服务技术转型(一步一步实现,会考虑兼容 .NET/.NET Core),以下是我整理的相关内容,如果你有更好的建议和意见,欢迎探讨~~~

关于 RPC/gRPC/HTTP/REST

因为服务调用方式是 Dubbo 和 Spring Cloud 重要不同点,了解 RPC/gRPC/HTTP/REST 相关概念,有助于对比 Dubbo 和 Spring Cloud。

RPC 是远端过程调用,其调用协议通常包含传输协议和编码协议

HTTP 严格来说跟 RPC 不是一个层级的概念,HTTP 本身也可以作为 RPC 的传输层协议

传输协议包含: 如著名的 gRPC 使用的 HTTP 2.0 协议,也有如 Dubbo 一类的自定义报文的 TCP 协议。编码协议包含: 如基于文本编码的 XML Json,也有二进制编码的 ProtoBuf Binpack 等。

所谓的效率优势是针对 HTTP 1.1 协议来讲的,HTTP 2.0 协议已经优化编码效率问题,像 gRPC 这种 RPC 库使用的就是 HTTP 2.0 协议。

在跨语言调用的时候,REST 风格直接把 HTTP 作为应用协议(直接和服务打交道),不同语言之间调用比较方便。

RPC 可以把 HTTP 作为一种传输协议(比如 gRPC 使用 HTTP 2.0 协议传输),本身还会封装一层 RPC 框架的应用层协议,不同语言之间调用需要依赖 RPC 协议(需要跨语言 RPC 库实现,比如 Thrift)。

问题:为什么 Dubbo 比 Spring Cloud 性能要高一些?

回答:因为 Dubbo 采用单一长连接和 NIO 异步通讯(保持连接/轮询处理),使用自定义报文的 TCP 协议,并且序列化使用定制 Hessian2 框架,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况,但不适用于传输大数据的服务调用。而 Spring Cloud 直接使用 HTTP 协议(但也不是强绑定,也可以使用 RPC 库,或者采用 HTTP 2.0 + 长链接方式(Fegin 可以灵活设置))。

另外,Martin Fowler 的 MicroServices 一文,其定义的服务间通信是 HTTP 协议的 REST API

Dubbo 是什么?

https://github.com/apache/incubator-dubbo

Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。简单的说,Dubbo 就是个服务框架,说白了就是个远程服务调用的分布式框架

Dubbo 框架

模块注解:

  • Provider: 暴露服务的服务提供方
  • Consumer: 调用远程服务的服务消费方
  • Registry: 服务注册与发现的注册中心
  • Monitor: 统计服务的调用次调和调用时间的监控中心
  • Container: 服务运行容器

流程详解:

  • 0 服务容器负责启动,加载,运行服务提供者(Standalone 容器)。
  • 1 服务提供者在启动时,向注册中心注册自己提供的服务(Zookeeper/Redis)。
  • 2 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 3 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 4 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 5 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心(根据数据可以动态调整权重)。

Dubbo 集群容错

面对服务消费方,当业务逻辑中需要调用一个服务时,真正调用的其实是 Dubbo 创建的一个 Proxy,该 Proxy 会把调用转化成调用指定的 Invoker(Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个(通过 LoadBalance),Invoker 封装了 Provider 地址及 Service 接口信息)。而在这一系列的委托调用的过程里就完成了服务治理的逻辑,最终完成调用。

Dubbo 特点

  • 远程通讯: 提供对多种基于长连接的 NIO 框架抽象封装(非阻塞 I/O 的通信方式,Mina/Netty/Grizzly),包括多种线程模型,序列化(Hessian2/ProtoBuf),以及“请求-响应”模式的信息交换方式。
  • 集群容错: 提供基于接口方法的透明远程过程调用(RPC),包括多协议支持(自定义 RPC 协议),以及软负载均衡(Random/RoundRobin),失败容错(Failover/Failback),地址路由,动态配置等集群支持。
  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

Dubbo 发展历程

  • 2008 年,阿里巴巴开始内部使用 Dubbo。
  • 2009 年初,发布 1.0 版本。
  • 2010 年初,发布 2.0 版本。
  • 2011 年 10 月,阿里巴巴宣布开源,版本为 2.0.7。
  • 2012 年 3 月,发布 2.1.0 版本。
  • 2013 年 3 月,发布 2.4.10 版本。
  • 2014 年 10 月,发布 2.3.11 版本,之后版本停滞。
  • 2017 年 9 月,阿里巴巴重启维护,重点升级所依赖 JDK 及组件版本,发布 2.5.4/5 版本。
  • 2017 年 10 月,发布 2.5.6 版本。
  • 2017 年 11 月,发布 2.5.7 版本,后期集成 Spring Boot。
  • 2014 年 10 月,当当网 Fork 了 Dubbo 版本,命名为 Dubbox-2.8.0,并支持 HTTP REST 协议。

Dubbo 负责人说明(重启维护是接受的采访):

阿里内部使用 HSF,原因业务属性规模有关。
这里就不得不提到目前的一些文章在谈到微服务的时候总是拿 Spring Cloud 和 Dubbo 来对比,需要强调的是 Dubbo 未来的定位并不是要成为一个微服务的全面解决方案,而是专注在 RPC 领域,成为微服务生态体系中的一个重要组件。至于大家关注的微服务化衍生出的服务治理需求,我们会在 Dubbo 积极适配开源解决方案,甚至启动独立的开源项目予以支持。
受众主要来自国内各友商以及个人开发者,希望将来能够将用户拓展到全球,代表国人在 RPC 领域与 gRPC(基于 HTTP 2.0)、Finagle 等竞争。

Spring Cloud 是什么?

https://github.com/spring-cloud

Spring Cloud 基于 Spring Boot,为微服务体系开发中的架构问题,提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等。

Spring Boot 是 Spring 的一套快速配置脚手架,使用默认大于配置的理念,用于快速开发单个微服务。

重点:

  • 基于 Spring Boot
  • 云服务、分布式框架集合(众多)

核心功能:

  • 分布式/版本化配置
  • 服务注册和发现
  • 路由
  • 服务和服务之间的调用
  • 负载均衡
  • 断路器
  • 分布式消息传递

Spring Cloud 完整技术

Spring Cloud 组件架构

流程:

  • 请求统一通过 API 网关(Zuul)来访问内部服务。
  • 网关接收到请求后,从注册中心(Eureka)获取可用服务。
  • 由 Ribbon 进行均衡负载后,分发到后端具体实例。
  • 微服务之间通过 Feign 进行通信处理业务。
  • Hystrix 负责处理服务超时熔断。
  • Turbine 监控服务间的调用和熔断相关指标。

Spring Cloud工具框架

  • Spring Cloud Config 配置中心,利用 Git 集中管理程序的配置。
  • Spring Cloud Netflix 集成众多Netflix的开源软件。
  • Spring Cloud Netflix Eureka 服务中心(类似于管家的概念,需要什么直接从这里取,就可以了),一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
  • Spring Cloud Netflix Hystrix 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
  • Spring Cloud Netflix Zuul 网关,是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Web 网站后端所有请求的前门。
  • Spring Cloud Netflix Archaius 配置管理 API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。
  • Spring Cloud Netflix Ribbon 负载均衡
  • Spring Cloud Netflix Fegin REST客户端
  • Spring Cloud Bus 消息总线,利用分布式消息将服务和服务实例连接在一起,用于在一个集群中传播状态的变化。
  • Spring Cloud for Cloud Foundry 利用 Pivotal Cloudfoundry 集成你的应用程序。
  • Spring Cloud Cloud Foundry Service Broker 为建立管理云托管服务的服务代理提供了一个起点。
  • Spring Cloud Cluster 集群工具,基于 Zookeeper, Redis, Hazelcast, Consul 实现的领导选举和平民状态模式的抽象和实现。
  • Spring Cloud Consul 基于 Hashicorp Consul 实现的服务发现和配置管理。
  • Spring Cloud Security 安全控制,在 Zuul 代理中为 OAuth2 REST 客户端和认证头转发提供负载均衡。
  • Spring Cloud Sleuth 分布式链路监控,SpringCloud 应用的分布式追踪系统,和 Zipkin,HTrace,ELK 兼容。
  • Spring Cloud Data Flow 一个云本地程序和操作模型,组成数据微服务在一个结构化的平台上。
  • Spring Cloud Stream 消息组件,基于 Redis,Rabbit,Kafka 实现的消息微服务,简单声明模型用以在 Spring Cloud 应用中收发消息。
  • Spring Cloud Stream App Starters 基于 Spring Boot 为外部系统提供 Spring 的集成。
  • Spring Cloud Task 短生命周期的微服务,为 Spring Booot 应用简单声明添加功能和非功能特性。
  • Spring Cloud Task App Starters。
  • Spring Cloud Zookeeper 服务发现和配置管理基于 Apache Zookeeper。
  • Spring Cloud for Amazon Web Services 快速和亚马逊网络服务集成。
  • Spring Cloud Connectors 便于PaaS应用在各种平台上连接到后端像数据库和消息经纪服务。
  • Spring Cloud Starters (项目已经终止并且在 Angel.SR2 后的版本和其他项目合并)
  • Spring Cloud CLI 命令行工具,插件用 Groovy 快速的创建 Spring Cloud 组件应用。

Dubbo 一些优点

  • Dubbo 支持 RPC 调用,服务之间的调用性能会很好。
  • 支持多种序列化协议,如 Hessian、HTTP、WebService。
  • Dobbo Admin后台管理功能强大,提供了路由规则、动态配置、访问控制、权重调节、均衡负载等功能。
  • 在国内影响力比较大,中文社区文档较为全面
  • 阿里最近重启维护

Dubbo 一些问题

  • Registry 严重依赖第三方组件(zookeeper 或者 redis),当这些组件出现问题时,服务调用很快就会中断。
  • Dubbo 只支持 RPC 调用。使得服务提供方(抽象接口)与调用方在代码上产生了强依赖,服务提供者需要不断将包含抽象接口的 jar 包打包出来供消费者使用。一旦打包出现问题,就会导致服务调用出错,并且以后发布部署会成很大问题(太强的依赖关系)。
  • 另外,以后要兼容 .NET Core 服务,Dubbo RPC 本身不支持跨语言(可以用跨语言 RPC 框架解决,比如 Thrift、gRPC(重复封装了),或者自己再包一层 REST 服务,提供跨平台的服务调用实现,但相对麻烦很多)
  • Dubbo 只是实现了服务治理,其他微服务框架并未包含,如果需要使用,需要结合第三方框架实现(比如分布式配置用淘宝的 Diamond、服务跟踪用京东的 Hydra,但使用相对麻烦些),开发成本较高,且风险较大。
  • 社区更新不及时(虽然最近在疯狂更新),但也难免阿里以后又不更新了,就尴尬了。
  • 主要是国内公司使用,但阿里内部使用 HSF,相对于 Spring Cloud,企业应用会差一些。

Spring Cloud 的一些优点

  • 有强大的 Spring 社区、Netflix 等公司支持,并且开源社区贡献非常活跃
  • 标准化的将微服务的成熟产品和框架结合一起,Spring Cloud 提供整套的微服务解决方案,开发成本较低,且风险较小
  • 基于 Spring Boot,具有简单配置、快速开发、轻松部署、方便测试的特点。
  • 支持 REST 服务调用,相比于 RPC,更加轻量化和灵活(服务之间只依赖一纸契约,不存在代码级别的强依赖),有利于跨语言服务的实现,以及服务的发布部署。另外,结合 Swagger,也使得服务的文档一体化
  • 提供了 Docker 及 Kubernetes 微服务编排支持。
  • 国内外企业应用非常多,经受了大公司的应用考验(比如 Netfilx 公司),以及强大的开源社区支持。

Spring Cloud 的一些问题

  • 支持 REST 服务调用,可能因为接口定义过轻,导致定义文档与实际实现不一致导致服务集成时的问题(可以使用统一文档和版本管理解决,比如 Swagger)。
  • 另外,REST 服务调用性能会比 RPC 低一些(但也不是强绑定)
  • Spring Cloud 整合了大量组件,相关文档比较复杂,需要针对性的进行阅读。

服务调用方式的不同

Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式。严格来说,这两种方式各有优劣。虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生 RPC 带来的问题。而且 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更加合适。

Dubbo 和 Spring Cloud 对比

Dubbo 专注 RPC 和服务治理,Spring Cloud 则是一个微服务架构生态。

ZooKeeper 和 Eureka 的区别

鉴于服务发现对服务化架构的重要性,Dubbo 实践通常以 ZooKeeper 为注册中心(Dubbo 原生支持的 Redis 方案需要服务器时间同步,且性能消耗过大)。针对分布式领域著名的 CAP 理论(C——数据一致性,A——服务可用性,P——服务对网络分区故障的容错性),Zookeeper 保证的是 CP ,但对于服务发现而言,可用性比数据一致性更加重要,AP 胜过 CP,而 Eureka 设计则遵循 AP 原则
Spring Cloud 支持 Consul(CA)和 Zookeeper,但不推荐使用。

网易考拉选型参考

当前开源上可选用的微服务框架主要有 Dubbo、Spring Cloud 等,鉴于 Dubbo 完备的功能和文档且在国内被众多大型互联网公司选用,考拉自然也选择了 Dubbo 作为服务化的基础框架。其实相比于 Dubbo,Spring Cloud 可以说是一个更完备的微服务解决方案,它从功能性上是 Dubbo 的一个超集,个人认为从选型上对于一些中小型企业 Spring Cloud 可能是一个更好的选择。提起 Spring Cloud,一些开发的第一印象是 HTTP + JSON 的 REST 通信,性能上难堪重用,其实这也是一种误读。微服务选型要评估以下几点:内部是否存在异构系统集成的问题;备选框架功能特性是否满足需求;HTTP 协议的通信对于应用的负载量会否真正成为瓶颈点(Spring Cloud 也并不是和 HTTP + JSON 强制绑定的,如有必要 Thrift、ProtoBuf 等高效的 RPC、序列化协议同样可以作为替代方案);社区活跃度、团队技术储备等。作为已经没有团队持续维护的开源项目,选择 Dubbo 框架内部就必须要组建一个维护团队,先不论你要准备要集成多少功能做多少改造,作为一个支撑所有工程正常运转的基础组件,问题的及时响应与解答、重大缺陷的及时修复能力就已足够重要。

Dubbo 和 Spring Cloud 比喻

使用 Dubbo 构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;而 Spring Cloud 就像品牌机,在 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。

.NET Core的兼容方案

若使用 Spring Cloud,.NET Core 兼容 Spring Cloud 比较好实现,因为基于 REST 服务调用,可以自行实现其服务(Eureka 提供 REST API 进行服务注册),也已有成熟的开源框架如 Steeltoe

官方介绍:

Steeltoe is an open source project that enables .NET developers to implement industry standard best practices when building resilient microservices for the cloud. The Steeltoe client libraries enable .NET Core and .NET Framework apps to easily leverage Netflix Eureka, Hystrix, Spring Cloud Config Server, and Cloud Foundry services.

关于 Service Mesh

2017 年底,非侵入式的 Service Mesh 技术从萌芽到走向了成熟。

Service Mesh 又译作“服务网格”,作为服务间通信的基础设施层

如果用一句话来解释什么是 Service Mesh,可以将它比作是应用程序或者说微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控。对于编写应用程序来说一般无须关心 TCP/IP 这一层(比如通过 HTTP 协议的 RESTful 应用),同样使用 Service Mesh 也就无须关系服务之间的那些原来是通过应用程序或者其他框架实现的事情,比如 Spring Cloud、OSS,现在只要交给 Service Mesh 就可以了。

总结

关于 Dubbo 和 Spring Cloud 的相关概念和对比,上面已经叙述的很清楚了,我个人比较倾向于 Spring Cloud,原因就是真正的微服务框架、提供整套的组件支持、使用简单方便、强大的社区支持等等,另外,因为考虑到 .NET/.NET Core 的兼容处理,RPC 并不能很好的实现跨语言(需要借助跨语言库,比如 gRPC、Thrift,但因为 Dubbo 本身就是“gRPC”,在 Dubbo 之上再包一层 gRPC,有点重复封装了),而 HTTP REST 本身就是支持跨语言实现,所以,Spring Cloud 这一点还是非常好的(Dubbox 也支持,但性能相比要差一些)。

但凡事无绝对,每件事物有好的地方也有不好的地方,总的来说,Dubbo 和 Spring Cloud 的主要不同体现在两个方面:服务调用方式不同专注点不同(生态不同)

最后,关于 Service Mesh,因为是很新的概念(去年年底才火起来),相关的框架并未真正用于生产环境,所以这边就不考虑了,但以后可能会发展的非常好。

参考资料:

Apache Shiro 官网地址:http://shiro.apache.org/

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

shiro是一个强大而且简单易用的Java安全框架,主要功能有认证(就是登陆验证),授权(就是权限管理),加密(就是密码加密),session管理。适用于各种大型或者小型企业应用。和Spring Security比较而言,确实更加简单而且灵活易懂。

1. shiro中的重要概念

要理解shiro,先要理解框架的几个概念:

  1. Subject: 代表当前登陆或者访问的用户;

2)Principals:一般指用户名等,唯一表明Subject身份也就是当前用户身份的东西;

3)Credentials:凭证,一般指密码,对当前登陆用户进行验证;

4)Realms:域,一般是指存储用户信息(用户名,密码,权限,角色)的数据库,也就是保存用户权限等信息的数据源

5)SecurityManager:shiro安全管理的顶级对象。它集合或者说调用所有其它相关组件,负责所有安全和权限相关处理过程,就像一个中央集权政府;

2. shiro的子系统

上面我们说到shiro的主要功能有:认证,授权,加密,session管理等。而每一个主要功能对应于shiro的一个子系统:

下面正对每一个子系统分别介绍。

3. Authentication认证子系统

认证子系统,就是处理用户登录,验证用户登录。我们前面讲到Subject代表当前用户,而Principal和credential分别就代表用户名和密码。登录认证时,其实就是调用的 Subject.login(AuthenticationToken token)方法,AuthenticationToken是一个接口:

复制代码

public interface AuthenticationToken extends Serializable { /** * Returns the account identity submitted during the authentication process.*/ Object getPrincipal(); /** * Returns the credentials submitted by the user during the authentication process that verifies
* the submitted {@link #getPrincipal() account identity}.*/ Object getCredentials();
}

复制代码

登录时就会分别调用它的实现类的 getPrincipal() 和 getCredentials() 来获得用户名(Principal:主体)和密码(Credentials:凭证)。一般实际中我们传给Subject.login()方法的是UsernamePasswordToken 类的对象,它实现了该接口:

一般我们new一个UsernamePasswordToken的对象:UsernamePasswordToken token = new UsernamePasswordToken(“xxxusername”, “xxxpassword”);, 然后 subject.login(token); 就前去登录。相关代码一般如下:

复制代码

@RequestMapping(value="/loginController", method=RequestMethod.POST) public String login(String userName, String password, String rememberMe, String type, HttpServletRequest req) { String error \= null;
    Subject subject \= SecurityUtils.getSubject();
    UsernamePasswordToken token \= new UsernamePasswordToken(userName, password);
    if(rememberMe != null && "true".equals(rememberMe))
        token.setRememberMe(true);    // 记住我        
    try {
        subject.login(token);
    } catch (UnknownAccountException | IncorrectCredentialsException e1) {
        error \= "用户名或密码错误";
    }catch(ExcessiveAttemptsException e){
        userService.lockAccountByNo(no); // 锁定账户
        error = "超过了尝试登录的次数,您的账户已经被锁定。";
    }catch (AuthenticationException e) {    // 其他错误
        if(e.getMessage() != null)
            error \= "发生错误:" + e.getMessage(); else error \= "发生错误,无法登录。";
    }  
    // .. ...

复制代码

Authentication 子系统会将password加密,然后使用username和加密之后的password和从Realm(一般是数据库)中根据usename获得的密码进行比较,相同就登录成功,不相同同就登录失败,或者用户名不存在也登录失败。就怎么简单。当然从Realm中根据用户名查找用户的过程是需要我们自己编码实现的。该功能的实现,shiro提供了抽象类 AuthenticatingRealm 专门用于从Realm中获得认证信息。所以我们可以继承 抽象类 AuthenticatingRealm,然后实现其中的抽象方法:

复制代码

/** * A top-level abstract implementation of the Realm interface that only implements authentication support
* (log-in) operations and leaves authorization (access control) behavior to subclasses. */
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable { //TODO - complete JavaDoc
private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class); // … …
/** * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
* authentication token.
*


* For most datasources, this means just ‘pulling’ authentication data for an associated subject/user and nothing
* more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific
* log-in logic in addition to just retrieving data - it is up to the Realm implementation.*/
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

复制代码

我们只要实现 protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 方法就可以了,其它的shiro会回调该方法,进行登录认证。而实现该方法就是直接从数据源中根据 AuthenticationToken 获得数据就行了。

除了这种方法之外,其实我们也可以使用 AuthenticatingRealm 的子类 AuthorizingRealm,它本来是用于权限认证的Realm,但是因为他继承了 AuthenticatingRealm,所以实际上我们只要继承 AuthorizingRealm,然后实现它的抽象方法就行了。同时搞定 登录认证 和 权限认证(访问控制):

复制代码

public class UserRealm extends AuthorizingRealm {
@Autowired private UserService userService;

@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String userName \= (String)principals.getPrimaryPrincipal();        User user = userService.getUserByName(userName);
    SimpleAuthorizationInfo authorizationInfo \= new SimpleAuthorizationInfo();
    authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));
    authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));
    return authorizationInfo;
}    
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String userName\= (String)token.getPrincipal();
    User user = userService.getUserByName(userName); if(user == null) { throw new UnknownAccountException();//没找到账户

} if(user.getLocked() == 0) { throw new LockedAccountException(); //帐号锁定
} if(user.getLocked() == 2){ throw new AuthenticationException(“account was inactive”);
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(), // 用户名
user.getPassword(), // 密码
ByteSource.Util.bytes(user.getCredentialsSalt()), // salt
getName() // realm name
); return authenticationInfo;
}
@Override public void clearCachedAuthorizationInfo(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals);
}
@Override public void clearCachedAuthenticationInfo(PrincipalCollection principals) { super.clearCachedAuthenticationInfo(principals);
}
@Override public void clearCache(PrincipalCollection principals) { super.clearCache(principals);
} public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
} public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
} public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}

复制代码

上面的 doGetAuthorizationInfo 方法,会在权限认证也就是访问控制时,被回调,而 doGetAuthenticationInfo 方法会在登录认证时被回调,返回的 AuthenticationInfo类型的对象,会和用户登录时输入的 用户名和密码(加密之后的)进行比较,相同则登录成功,反之则登录失败。

其实还有更加简单的方法,因为shiro提供了实现了 AuthorizingRealm 中的抽象方法的子类:

比如在数据库环境中,我们就可以直接使用 JdbcRealm,一是可以配置它的相关SQL语句,二是继承它,覆盖它的方法。CasRealm用户单点登录环境。

复制代码

/** * Realm that allows authentication and authorization via JDBC calls. The default queries suggest a potential schema
* for retrieving the user’s password for authentication, and querying for a user’s roles and permissions. The
* default queries can be overridden by setting the query properties of the realm.
*


* If the default implementation
* of authentication and authorization cannot handle your schema, this class can be subclassed and the
* appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)},
* {@link #getRoleNamesForUser(java.sql.Connection,String)}, and/or {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}
*


* This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.*/
public class JdbcRealm extends AuthorizingRealm { //TODO - complete JavaDoc
/*-——————————————-
| C O N S T A N T S |
============================================*/
/** * The default query used to retrieve account data for the user. */
protected static final String DEFAULT_AUTHENTICATION_QUERY = “select password from users where username = ?”; /** * The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN. */
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = “select password, password_salt from users where username = ?/** * The default query used to retrieve the roles that apply to a user. */
protected static final String DEFAULT_USER_ROLES_QUERY = “select role_name from user_roles where username = ?”; /** * The default query used to retrieve permissions that apply to a particular role. */
protected static final String DEFAULT_PERMISSIONS_QUERY = “select permission from roles_permissions where role_name = ?”; private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class); /** * Password hash salt configuration.


    *
  • NO_SALT - password hashes are not salted.

  • *
  • CRYPT - password hashes are stored in unix crypt format.

  • *
  • COLUMN - salt is in a separate column in the database.

  • *
  • EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called
    * to get the salt
*/
public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL}; /*-——————————————-
| I N S T A N C E V A R I A B L E S |
============================================*/
protected DataSource dataSource; protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY; protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY; protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY; protected boolean permissionsLookupEnabled = false; protected SaltStyle saltStyle = SaltStyle.NO_SALT;

复制代码

我们可以对上面给出的sql语句进行配置,修改成对应于我们数据库中表的sql语句,也可以继承该类,然后覆盖doGetAuthenticationInfo, getRoleNamesForUser(), getPermissions()三个方法。

4. Authorization 授权子系统(访问控制)

上一节中我们已经介绍了如何获得用户所拥有的权限,在需要判断用户是否有某权限或者角色时,会自动回调方法 doGetAuthorizationInfo 来获得用户的角色和权限,我们只需要在 该方法中从Realm也就是数据库表中获得相关信息。我们先看一下shiro是如何表示角色和权限的,这一点比较重要:

复制代码

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String no \= (String)principals.getPrimaryPrincipal();
    User user \= userService.getUserByNo(no);
    SimpleAuthorizationInfo authorizationInfo \= new SimpleAuthorizationInfo();
    authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));
    authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId())); return authorizationInfo;
} 

复制代码

我们看到 doGetAuthorizationInfo 方法中使用了 SimpleAuthorizationInfo 类封装 Role 和 Permission.

复制代码

/** * Simple POJO implementation of the {@link AuthorizationInfo} interface that stores roles and permissions as internal
* attributes.
* @see org.apache.shiro.realm.AuthorizingRealm
* @since 0.9 */
public class SimpleAuthorizationInfo implements AuthorizationInfo { /** * The internal roles collection. */
protected Set roles; /** * Collection of all string-based permissions associated with the account. */
protected Set stringPermissions; /** * Collection of all object-based permissions associaed with the account. */
protected Set objectPermissions; /** * Default no-argument constructor. */
public SimpleAuthorizationInfo() {
}

复制代码

我们看到,roles 和 stringPermissions 都是 String 类型的 Set, 也就是说,它们都是使用字符串来表示你拥有某个角色或者拥有某个权限的

1) 两种访问控制方式:

SimpleAuthorizationInfo 封装了角色和权限,其实这也说明了实现“访问控制”两种方式:一是 “基于角色的访问控制”;而是“基于资源的访问控制”。所谓的访问控制,是指对于某个资源,当前用户是否有访问的权限。基于角色的访问控制是一种比较粗粒度的访问控制方式,只要你具有了某个或某几个角色,那么你就可以访问某资源。而基于资源的访问控制,是判断你针对该资源是否有某权限,有才能访问,粒度更细,你是否有某权限,可以根据你有哪些角色,然后改角色有哪些权限来判断的,当然也可以不引入角色的概念,直接判断你是否拥有某些权限。当然两种访问方式可以单独使用,也可以混合使用。比如对于比较简单的权限控制,你可以仅仅只使用基于角色的访问控制,仅仅引入角色表,不需要权限表都可以。混合使用是指,你可以同时要求用户具有某角色并且具有某些权限,才能访问某资源。所以shiro的权限控制时极其灵活的(当然也可以不引入角色表,仅仅引入权限表)。

2)权限的字符串表示方式

上面说到 角色 和 权限 都是使用字符串来表示的,其实 shiro 提供了一套比较强大有点复杂的权限字符串表示格式(分为:分割的三个部分):

资源:操作:对象实例ID” 表示:对那个资源的哪个实例可以进行哪些操作,支持通配符。

多个操作需要使用 “,” 逗号分割,而 “*” 放在三个位置上,分别表示:任意资源,任意操作,任意实例。

比如:”user:delete:1” 就表示 对user表的id等于1对应的数据或者对象,可以进行删除操作。其实资源表现实现可以是对象,其实最终是对应到数据库表中的记录。

在比如:”user:update,delete” 就表示 对user表(的任意实例)进行更新和删除操作。”user:update,delete” 其实就等价于 “user:update,delete:*”

所以 shiro 的访问控制可以控制到具体实例,或者说具体哪条数据库记录,也可以在表级别控制如果省略掉 对象实例ID部分,就是在表级别控制

3)权限相关表的设计

1> 如果对于简单的情况,可以只使用“基于角色的访问控制”粗粒度方式,不涉及到权限,仅仅只通过判断是否有某角色来判断访问控制,那么就只需要增加一个角色表(roles) 和 一个角色(roles)和用户(user)的多对多的一个中间表——用户角色表(user_role)。

2> 如果仅仅使用权限来控制访问,那么就可以仅仅只增加一个权限表(priv)和一个用户和权限的多对多的一个中间表——用户权限表(user_priv).

3> 如果既要用到角色,又要用到权限(权限根据角色推算出来),那么就要增加:角色表,用户角色表,权限表,角色权限表。

4> 其实还有一种情况:就是角色和权限没有关系,那么就可以增加:角色表,用户角色表,权限表,用户权限表。不过这种方式不同符合常规。

5. Cryptography 加密子系统

shiro提供了很完备而且十分易用的加密解密功能。该子系统分为两个部分:一是基于hash的单向加密算法二是基于经典加密解密算法,密码是可以解密的出明文的;一般而言,对于登录用户的密码的加密都是采用单向的hash加密算法,因为如果密码可以被解密的话,一旦数据库被攻破了,那么所有用户的密码就都可以被解密成明文;但是单向的hash加密算法,没有这样的风险。单向的hash加密算法,就算你获得了数据库的中保存的密码密文,知道了密文对应的salt,甚至知道了使用的是什么hash算法,你都无法反向推算出密码的明文!因为hash是单向的,它没有对应的反向推算算法(也就是没有解密方法)。那么知道了密文,你是无法反推出密码明文的。这也是单向hash加密算法的妙处。

1)单向hash加密算法

shiro提供的单向hash加密算法的相关工具类如下:

我们看到提供了 Md2, Md5, Sha1, Sha256, Sha384, Sha512 等等的hash算法。一般而言Md2/Md5系列的算法已经被证实安全性存在不足。所以一般使用Sha系列的算法。其实看下源码的话,就知道上面所有的hash算法都是继承与 SimpleHash 类,SimpleHash 才是真正的实现者,而其他的比如 Sha256Hash 不过是传入本算法需要的参数,然后调用了 SimpleHash 中hash加密算法而已,看下源码:

复制代码

public class Sha256Hash extends SimpleHash {
public static final String ALGORITHM_NAME = “SHA-256”; public Sha256Hash() { super(ALGORITHM_NAME);
} public Sha256Hash(Object source) { super(ALGORITHM_NAME, source);
} public Sha256Hash(Object source, Object salt) { super(ALGORITHM_NAME, source, salt);
} public Sha256Hash(Object source, Object salt, int hashIterations) { super(ALGORITHM_NAME, source, salt, hashIterations);
} public static Sha256Hash fromHexString(String hex) {
Sha256Hash hash = new Sha256Hash();
hash.setBytes(Hex.decode(hex)); return hash;
} public static Sha256Hash fromBase64String(String base64) {
Sha256Hash hash = new Sha256Hash();
hash.setBytes(Base64.decode(base64)); return hash;
}
}

复制代码

我们看到都是使用 super() 调用父类的方法。根据上面截图中提高的相关类,可以有三种方法来实现密码锁需要的hash加密过程:

1> 直接使用  Sha256Hash/Md5Hash 等类,比如:

String sha256 = new Sha256Hash(“admin”, “11d23ccf28fc1e8cbab8fea97f101fc1d”, 2).toString();

根据Sha256Hash的构造函数,”admin” 为需要加密的密码明文,”11d23ccf28fc1e8cbab8fea97f101fc1d” 为加密需要的salt, 2 是迭代次数,也就是hash次数。最后调用 .toString() 就获得了密文。很简单。

2> 使用 Sha256Hash/Md5Hash 等类 父类 SimpleHash ,比如:

sha1 = new SimpleHash(“sha-256”, “admin”, “11d23ccf28fc1e8cbab8fea97f101fc1d”, 2).toString();

看到,我们传入了hash算法的名称 “sha-256”, 剩下的参数和 Sha256Hash 的一样。

3> 使用 DefaultHashService 和 HashRequest 二者结合来加密:

复制代码

    DefaultHashService hashService = new DefaultHashService(); // hashService.setHashAlgorithmName("SHA-256"); // hashService.setPrivateSalt(new SimpleByteSource("123")); // hashService.setGeneratePublicSalt(false); // hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator()); // hashService.setHashIterations(2); //

HashRequest hashRequest = new HashRequest.Builder()
.setSource(ByteSource.Util.bytes(“admin112358”))
.setSalt(“11d23ccf28fc1e8cbab8fea97f101fc1d”)
.setAlgorithmName(“SHA-256”)
.setIterations(2).build();

    System.out.println(hashService.computeHash(hashRequest).toHex());

复制代码

我们看到 HashRequest 类专门提供各种加密需要的参数,密码明文,salt, hash算法,迭代次数。这里有个坑,不要调用DefaultHashService的方法来设置各种加密需要的参数(特别是salt相关的参数),而使用专门的类 HashRequest来提供各种参数,因为使用 DefaultHashService 你是无法设置对 salt 的,也无法获得 salt ,而最终我们是需要将 salt 存放入数据库的,DefaultHashService只能设置 privateSalt, 它hash时最终使用的salt是privateSlat 和 自动生成的 publicSalt,二者合成得到的,合成的结果并没有提供方法来使我们获得它。另外DefaultHashService有一个坑:如果你调用方法hashService.setPrivateSalt(new SimpleByteSource(“123”));设置了privateSalt, 即使你调用了hashService.setGeneratePublicSalt(false);方法,它还是会随机生成publicSalt的。另外 HashRequest 中提供的参数会覆盖DefaultHashService设置的相应参数。

相比较而言,肯定是直接使用 Sha256Hash/Md5Hash 等类来得最简单而直接。

2)双向经典加密/解密算法

主要提供了 AES 和 Blowfish两种加密解密算法。

1> AES:

复制代码

    AesCipherService aesCipherService = new AesCipherService(); 
    aesCipherService.setKeySize(128); // 设置key长度 // 生成key 
    Key key = aesCipherService.generateNewKey();   

        // 加密
String encrptText = aesCipherService.encrypt(text.getBytes(), key.getEncoded()).toHex(); // 解密 String text2 = new String(aesCipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes());
System.out.println(text2.equals(text));

复制代码

Key 表示 秘钥,就相当于 Hash 算法中的 salt,秘钥不同,最终的密文也就不同。不同的是解密时是需要使用加密时相同的秘钥才能解密成功。

2> Blowfish:

    BlowfishCipherService blowfishService = new BlowfishCipherService();
    blowfishService.setKeySize(128);
    Key bKey \= blowfishService.generateNewKey();
    String encrpt \= blowfishService.encrypt("admin".getBytes(), bKey.getEncoded()).toHex();
    String dec \= new String(blowfishService.decrypt(Hex.decode(encrpt), bKey.getEncoded()).getBytes());
    System.out.println("admin".equals(dec));

3> 使用 DefaultBlockCipherService 实现加密解密:

复制代码

    //使用Java的JCA(javax.crypto.Cipher)加密API,常见的如 AES, Blowfish
    DefaultBlockCipherService cipherService = new DefaultBlockCipherService("AES");
    cipherService.setKeySize(128); //生成key
    bKey = cipherService.generateNewKey();
    text \= "admin"; //加密
    encrptText = cipherService.encrypt(text.getBytes(), key.getEncoded()).toHex(); //解密
    text2 = new String(cipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes());
    System.out.println(text.equals(text2));

复制代码

DefaultBlockCipherService(BlockCipher)是分组加密的意思,分组是指加密的过程是先进行分组,然后加密。AES 和 Blowfish都是分组加密算法。

3) 密码加密和密码验证

注册时一般涉及到密码加密,登录时涉及到密码验证。通过上面介绍的 加密算法,完全可以自己实现密码加密和密码验证。但是其实shiro也提供了相应的类:

DefaultPasswordService 和 HashedCredentialsMatcher。虽然提供了,其实  DefaultPasswordService 卵用都没有,因为他没有提供获取或者设置 salt 的方法,而 salt 是我们需要存入数据库的。所以密码加密我们是不使用 DefaultPasswordService 的,而是根据前面的介绍自己写。至于密码验证我们应该继承 HashedCredentialsMatcher,然后重写它的 doCredentialsMatch() 方法即可:

复制代码

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { private Cache<String, AtomicInteger> passwordRetryCache; public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
passwordRetryCache = cacheManager.getCache(“passwordRetryCache”);
}
@Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String)token.getPrincipal();
AtomicInteger retryCount = passwordRetryCache.get(username); if(retryCount == null) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
} if(retryCount.incrementAndGet() > 5) { throw new ExcessiveAttemptsException(“超过了尝试登录的次数,您的账户已经被锁定。”);
} boolean matches = super.doCredentialsMatch(token, info); if(matches) {
passwordRetryCache.remove(username);
} return matches;
}
}

复制代码

super.doCredentialsMatch(token, info)调用了父类的方法:

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials \= hashProvidedCredentials(token, info);
    Object accountCredentials \= getCredentials(info); return equals(tokenHashedCredentials, accountCredentials);
}

复制代码

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt \= null; if (info instanceof SaltedAuthenticationInfo) {
        salt \= ((SaltedAuthenticationInfo) info).getCredentialsSalt();
    } else { //retain 1.0 backwards compatibility:
        if (isHashSalted()) {
            salt \= getSalt(token);
        }
    } return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}

复制代码

我们看到 AuthenticationToken token 加密时需要的 salt 来自于 AuthenticationInfo info:

salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();

AuthenticationToken token 是登录时页面传过来的用户名,明文密码等参数,AuthenticationInfo info却是从数据库中获得的用户名密码密文,salt等参数。equals(tokenHashedCredentials, accountCredentials); 验证: 明文密码使用相同的salt加密之后,获得的密文是否和数据库中的密码密文一致。一致,则密码验证通过。

6. Session Management会话管理子系统

shiro中session的最大不同时,它可以使用再非web环境中。对于JavaSE的环境也可以使用session的功能,因为他实现了一套不依赖于web容器的session机制。shiro提供了三个默认的实现:

1> DefaultSessionManager: DefaultSecurityManager使用的默认实现,用于JavaSE环境;

2> ServletContainerSessionManager: DefaultWebSecurityManager使用的默认实现,用于web环境,其直接使用Servlet容器的会话;

3> DefaultWebSessionManager: 用于web环境的实现,可以替代ServletContainerSessionManager,自己维护会话,直接替代Servlet容器的会话管理;

在web环境中默认使用的是 ServletContainerSessionManager,其使用的就是Servlet容器的会话,这一点,我们可以从它的源码中可以看出来:

复制代码

public class ServletContainerSessionManager implements WebSessionManager {//TODO - read session timeout value from web.xml
public ServletContainerSessionManager() {
} public Session start(SessionContext context) throws AuthorizationException { return createSession(context);
} public Session getSession(SessionKey key) throws SessionException { if (!WebUtils.isHttp(key)) {
String msg = “SessionKey must be an HTTP compatible implementation.”; throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(key);
Session session = null;
HttpSession httpSession = request.getSession(false); if (httpSession != null) {
session = createSession(httpSession, request.getRemoteHost());
} return session;
}
protected Session createSession(SessionContext sessionContext) throws AuthorizationException { if (!WebUtils.isHttp(sessionContext)) {
String msg = “SessionContext must be an HTTP compatible implementation.”; throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);
HttpSession httpSession = request.getSession(); //SHIRO-240: DO NOT use the ‘globalSessionTimeout’ value here on the acquired session. //see: https://issues.apache.org/jira/browse/SHIRO-240 String host = getHost(sessionContext); return createSession(httpSession, host);
}
public boolean isServletContainerSessions() { return true;
}
}

复制代码

DefaultWebSecurityManager默认使用的是ServletContainerSessionManager:

复制代码

/** * Default {@link WebSecurityManager WebSecurityManager} implementation used in web-based applications or any
* application that requires HTTP connectivity (SOAP, http remoting, etc).
* @since 0.2 */
public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager { //TODO - complete JavaDoc
private static final Logger log = LoggerFactory.getLogger(DefaultWebSecurityManager.class);
@Deprecated public static final String HTTP_SESSION_MODE = “http”;
@Deprecated public static final String NATIVE_SESSION_MODE = “native”; /** * @deprecated as of 1.2. This should NOT be used for anything other than determining if the sessionMode has changed. */ @Deprecated private String sessionMode; public DefaultWebSecurityManager() { super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}

复制代码

从 setSessionManager(new ServletContainerSessionManager()); 看到答案。

JavaScript使用MQTT - 农民子弟 - 博客园

Excerpt

1、MQTT Server使用EMQTTD开源库,自行安装配置; 2、JS使用Websocket连接通信。 3、JS的MQTT库为paho-mqtt,git地址:https://github.com/eclipse/paho.mqtt.javascript.git 代码如下: <!DOCTYP


1、MQTT Server使用EMQTTD开源库,自行安装配置;

2、JS使用Websocket连接通信。

3、JS的MQTT库为paho-mqtt,git地址:https://github.com/eclipse/paho.mqtt.javascript.git

代码如下:

复制代码

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
<span>&lt;!</span><span>DOCTYPE html</span><span>&gt;</span>
<span>&lt;</span><span>html</span><span>&gt;</span>

<span>&lt;</span><span>head</span><span>&gt;</span>
<span>&lt;</span><span>meta </span><span>charset</span><span>="utf-8"</span> <span>/&gt;</span>
<span>&lt;</span><span>title</span><span>&gt;&lt;/</span><span>title</span><span>&gt;</span>
<span>&lt;!--</span><span> &lt;script src=“https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js” type=“text/javascript”&gt; &lt;/script&gt; </span><span>--&gt;</span>
<span>&lt;</span><span>script </span><span>src</span><span>="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"</span><span> type</span><span>="text/javascript"</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span>

<span>&lt;</span><span>script</span><span>&gt;</span>
<span>var</span><span> hostname </span><span>=</span> <span>'</span><span>127.0.0.1</span><span>'</span><span>, </span><span>//</span><span>'192.168.1.2',</span>
<span> port </span><span>=</span> <span>8083</span><span>,
clientId </span><span>=</span> <span>'</span><span>client-mao2080</span><span>'</span><span>,
timeout </span><span>=</span> <span>5</span><span>,
keepAlive </span><span>=</span> <span>100</span><span>,
cleanSession </span><span>=</span> <span>false</span><span>,
ssl </span><span>=</span> <span>false</span><span>,
</span><span>//</span><span> userName = 'mao2080', </span>
<span>//</span><span> password = '123', </span>
<span> topic </span><span>=</span> <span>'</span><span>/World</span><span>'</span><span>;
client </span><span>=</span> <span>new</span><span> Paho.MQTT.Client(hostname, port, clientId);
</span><span>//</span><span>建立客户端实例 </span>
<span>var</span><span> options </span><span>=</span><span> {
invocationContext: {
host: hostname,
port: port,
path: client.path,
clientId: clientId
},
timeout: timeout,
keepAliveInterval: keepAlive,
cleanSession: cleanSession,
useSSL: ssl,
</span><span>//</span><span> userName: userName, </span>
<span>//</span><span> password: password, </span>
<span> onSuccess: onConnect,
onFailure: </span><span>function</span><span> (e) {
console.log(e);
s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, onFailure()}</span><span>"</span><span>;
console.log(s);
}
};
client.connect(options);
</span><span>//</span><span>连接服务器并注册连接成功处理事件 </span>
<span>function</span><span> onConnect() {
console.log(</span><span>"</span><span>onConnected</span><span>"</span><span>);
s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, onConnected()}</span><span>"</span><span>;
console.log(s);
client.subscribe(topic);
}

client.onConnectionLost </span><span>=</span><span> onConnectionLost;

</span><span>//</span><span>注册连接断开处理事件 </span>
<span> client.onMessageArrived </span><span>=</span><span> onMessageArrived;

</span><span>//</span><span>注册消息接收处理事件 </span>
<span>function</span><span> onConnectionLost(responseObject) {
console.log(responseObject);
s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, onConnectionLost()}</span><span>"</span><span>;
console.log(s);
</span><span>if</span><span> (responseObject.errorCode </span><span>!==</span> <span>0</span><span>) {
console.log(</span><span>"</span><span>onConnectionLost:</span><span>"</span> <span>+</span><span> responseObject.errorMessage);
console.log(</span><span>"</span><span>连接已断开</span><span>"</span><span>);
}
}

</span><span>function</span><span> onMessageArrived(message) {
s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, onMessageArrived()}</span><span>"</span><span>;
console.log(s);
console.log(</span><span>"</span><span>收到消息:</span><span>"</span> <span>+</span><span> message.payloadString);
}

</span><span>function</span><span> send() {
</span><span>var</span><span> s </span><span>=</span><span> document.getElementById(</span><span>"</span><span>msg</span><span>"</span><span>).value;
</span><span>if</span><span> (s) {
s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, content:</span><span>"</span> <span>+</span><span> (s) </span><span>+</span> <span>"</span><span>, from: web console}</span><span>"</span><span>;
message </span><span>=</span> <span>new</span><span> Paho.MQTT.Message(s);
message.destinationName </span><span>=</span><span> topic;
client.send(message);
document.getElementById(</span><span>"</span><span>msg</span><span>"</span><span>).value </span><span>=</span> <span>""</span><span>;
}
}

</span><span>var</span><span> count </span><span>=</span> <span>0</span><span>;

</span><span>function</span><span> start() {
window.tester </span><span>=</span><span> window.setInterval(</span><span>function</span><span> () {
</span><span>if</span><span> (client.isConnected) {
</span><span>var</span><span> s </span><span>=</span> <span>"</span><span>{time:</span><span>"</span> <span>+</span> <span>new</span><span> Date().Format(</span><span>"</span><span>yyyy-MM-dd hh:mm:ss</span><span>"</span><span>) </span><span>+</span> <span>"</span><span>, content:</span><span>"</span> <span>+</span><span> (count</span><span>++</span><span>) </span><span>+</span>
<span>"</span><span>, from: web console}</span><span>"</span><span>;
message </span><span>=</span> <span>new</span><span> Paho.MQTT.Message(s);
message.destinationName </span><span>=</span><span> topic;
client.send(message);
}
}, </span><span>1000</span><span>);
}

</span><span>function</span><span> stop() {
window.clearInterval(window.tester);
}

Date.prototype.Format </span><span>=</span> <span>function</span><span> (fmt) { </span><span>//</span><span>author: meizz </span>
<span>var</span><span> o </span><span>=</span><span> {
</span><span>"</span><span>M+</span><span>"</span><span>: </span><span>this</span><span>.getMonth() </span><span>+</span> <span>1</span><span>, </span><span>//</span><span>月份 </span>
<span>"</span><span>d+</span><span>"</span><span>: </span><span>this</span><span>.getDate(), </span><span>//</span><span>日 </span>
<span>"</span><span>h+</span><span>"</span><span>: </span><span>this</span><span>.getHours(), </span><span>//</span><span>小时 </span>
<span>"</span><span>m+</span><span>"</span><span>: </span><span>this</span><span>.getMinutes(), </span><span>//</span><span>分 </span>
<span>"</span><span>s+</span><span>"</span><span>: </span><span>this</span><span>.getSeconds(), </span><span>//</span><span>秒 </span>
<span>"</span><span>q+</span><span>"</span><span>: Math.floor((</span><span>this</span><span>.getMonth() </span><span>+</span> <span>3</span><span>) </span><span>/</span> <span>3</span><span>), </span><span>//</span><span>季度 </span>
<span>"</span><span>S</span><span>"</span><span>: </span><span>this</span><span>.getMilliseconds() </span><span>//</span><span>毫秒 </span>
<span> };
</span><span>if</span><span> (</span><span>/</span><span>(y+)</span><span>/</span><span>.test(fmt)) fmt </span><span>=</span><span> fmt.replace(RegExp.$</span><span>1</span><span>, (</span><span>this</span><span>.getFullYear() </span><span>+</span> <span>""</span><span>).substr(</span><span>4</span> <span>-</span><span> RegExp.$</span><span>1</span><span>.length));
</span><span>for</span><span> (</span><span>var</span><span> k </span><span>in</span><span> o)
</span><span>if</span><span> (</span><span>new</span><span> RegExp(</span><span>"</span><span>(</span><span>"</span> <span>+</span><span> k </span><span>+</span> <span>"</span><span>)</span><span>"</span><span>).test(fmt)) fmt </span><span>=</span><span> fmt.replace(RegExp.$</span><span>1</span><span>, (RegExp.$</span><span>1</span><span>.length </span><span>==</span> <span>1</span><span>) </span><span>?</span><span> (o[
k]) : ((</span><span>"</span><span>00</span><span>"</span> <span>+</span><span> o[k]).substr((</span><span>""</span> <span>+</span><span> o[k]).length)));
</span><span>return</span><span> fmt;
}
</span><span>&lt;/</span><span>script</span><span>&gt;</span>
<span>&lt;/</span><span>head</span><span>&gt;</span>

<span>&lt;</span><span>body</span><span>&gt;</span>
<span>&lt;</span><span>input </span><span>type</span><span>="text"</span><span> id</span><span>="msg"</span> <span>/&gt;</span>
<span>&lt;</span><span>input </span><span>type</span><span>="button"</span><span> value</span><span>="Send"</span><span> onclick</span><span>="send()"</span> <span>/&gt;</span>
<span>&lt;</span><span>input </span><span>type</span><span>="button"</span><span> value</span><span>="Start"</span><span> onclick</span><span>="start()"</span> <span>/&gt;</span>
<span>&lt;</span><span>input </span><span>type</span><span>="button"</span><span> value</span><span>="Stop"</span><span> onclick</span><span>="stop()"</span> <span>/&gt;</span>
<span>&lt;/</span><span>body</span><span>&gt;</span>

<span>&lt;/</span><span>html</span><span>&gt;</span>

复制代码

最近受累于测试环境每次发布都很麻烦,而且我们有多个测试环境,因此专门抽时间做了Jenkins的配置和研究。

折腾了两天终于绿灯以后,先来个截图,BlueOcean UI还是很nice的。

环境搭建

找一个干净点的Build服务器,然后开始持续集成之旅吧!

  1. 安装JDK

我安装了Oracle的JDK之后貌似发送邮件SSL方式有些问题,然而网上的方案貌似不能修复。stack上人家推荐安装openjdk的,我还没来及折腾,不过全新安装的话,推荐openjdk吧。

  1. Jenkins安装

Jenkins官网下载最新的安装包,选择对应的系统。安装都是无脑的包括启动,

  1. 安装.NET编译环境

这里有点弯路!刚开始我也是满怀期待的想尽量的让Build服务器干净一些,所以找的是独立的MSBuild方式,然而后续的过程证明依赖VS编译的我们离不开VS环境!

独立的MSBuild安装包:下载

当然我的推荐是直接安装Visual Studio,由于我们全部用.NET 4.6.1 所以直接安装了 Visual Studio 2015社区版,尽量少些东西!

以上就是基础的Build环境了。

配置持续集成Job

Jenkins的强大之处在于,所有的任务可以被拆分成粒度自由的Job。而Job之间可以通过很多方式来组织。例如正常的持续集成流程是:获取代码->Build->单元测试脚本->发布到相应环境->自动化测试脚本->Email报告。这里的所有步骤可以分拆成单独的job然后再串起来执行,不过这样需要很了解Jenkins,这不是今天要记录的。(其实我也没研究会。。。长路漫漫了)

  1. 新建Job

Job选择”自由风格的软件项目”,OK之后General选项卡中默认即可。唯一需要注意的是如果你的多个工程之间有关联,又或者你有很多工程想放在一起,那么我建议配置上”自定义的工作空间”。

如图,我指定了我的工作控件是yqnwork,我创建的所有job都这样设定,那样源代码都会被获取到这里,之后的工作都在一个根目录下进行,将大大方便处理。不然坑起来都是填不完的(我爬出来的!!)

  1. 源码管理

获取代码这里,我选择的是TFS,TFS不是原生支持的。如果选择Git/Subversion应该会更方便一些。

TFS需要插件支持,Team Foundation Server Plug-in,插件安装失败的先看Jenkins插件手动更新章节

安装完插件之后,重新编辑job,配置如下:

CollectionURL是TFS URL + 集合名称!
Project path是相对于集合的项目文件夹路径,必须要$开头,这些配置在VS源码管理中都可以自己看到的。

Credentials也是一个坑!坑了我大半天!务必要新建一个TFS帐号专门用于Jenkins,不然有workspace的冲突风险。我进了一直不能获取到最新版本的坑,google了半天,最后才发现的!
Local workfolder&Woekspace name根据实际填写。

  1. 构建 Build

构建触发器构建环境根据实际需要填写,这里默认即可。

.NET Build需要MSBuild插件的支持,如果根据前面的环境搭建已经安装了VS那么万事大吉!

安装完MSBuild Plugin需要配置下默认的MSBuild路径。在Global Tool Configuration中找到MSBuild节,新增一个配置如下:

注意依然有坑!!! MSBuild的路径需要根据.NET的版本来决定,.NET 4.5以下的貌似引用的是C:\Windows\Microsoft.NET\Framework64\v4.0.30319中的,4.5以上的才是在图中的路径,具体的自己尝试吧~

回到Job的配置:

选择刚才配置的MSBuild,注意我这里是直接发布Asp.NET MVC项目,所以我Build File的目标是csproj。如果是要编译整个工程,请选择对应的sln。
编译参数根据需要配置。

其中需要说明的是,如果是发布那么就按照我这里来。
publishProfile是发布的配置文件,这个建议在VS中手动创建一个配置针对Jenkins的发布配置,选择发布对象为文件系统,目录是重点!!!目录请选择Build服务器上对应的目录,建议还是发布到workspace中。workspace的目录可以参看我的workspace目录

VisualStudioVersion请选择对应的VS版本!!

这里我遇到一个坑,由于nuget版本太低导致dll获取错误编译失败。手动升级了工程中.nuget目录下的nuget.exe。(打到这里vs code死了。。。没保存。。。)

另外Nuget由于墙的问题很卡啊,我还手动在Build服务器上开启了代理。

  1. 附加的任务执行

由于我不想把不同环境的配置文件也在这个时候生成,被push到相应的环境(我们分QA、STA环境),所以这里我直接删掉了生成后的文件。选择添加一个windows command的步骤,执行del指令,这里本来可以用环境变量但是作为del指令的参数出了些问题,所以改用绝对路径!

关于这些配置文件我的想法是根据不同环境从TFS中获取不同的文件,这点在将来再讨论实现。

  1. 发布

发布一般都是使用FTP或者SSH,windows的服务器自然选择FTP。由于IIS自带FTP发布功能,因此首先配置IIS FTP发布功能。

先从MS的Microsoft Web Platform Installer中安装 IIS:FTP发布服务&IIS:FTP扩展性。 然后打开IIS之后选择目标网站,右键里会出现添加FTP发布,配置如下:


建议是启用虚拟的主机名,这样便于多个站点的FTP分开维护,当然你可以选择默认的IP,然后修改成指定的website目录的FTP,这样很方便,但是不建议哦!

然后选择”基本”的身份验证,授权给指定的账户,建议单独建立一个Jenkins账户用来发布!这样IIS FTP就配置完成了,测是的话可以用command的ftp命令来测试。

如果使用虚拟主机名配置了FTP,访问时的帐号是 www.demo.com|jenkins!!! 域名 + “|” + 账户名

然后回到Jenkins,添加FTP配置:首先安装插件Publish over FTP。然后到系统设置中添加FTP帐号。依然提醒注意帐号名称。

接着回到Job配置FTP:

Source files这里最好点击问号,看看详细的正则规则,全部文件夹及文件就配置成**/*
我这里由于我每个项目都publish到指定文件夹,因为带了前缀。然后发布到服务器的FTP地址是不需要这些前缀文件夹的,因此需要配置Remove prefix来移除。

‘Transfer Set’我研究了下还有更复杂的功能可以配置,自行研究吧。

  1. 备份发布

由于每次发布都会直接删掉原来的发布文件,因此一般发布完成之后都会备份一份文件,这样做也便于回滚哦~!

备份的话我选择执行一段windows command,直接贴Command:

  1. 邮件配置

这个功能不提了,配置很简单,先到系统配置下邮件的发送服务器,然后添加一个步骤Email Notification就可以了。

workspace目录

这里贴一下我的workspace目录:

bak:我用来存放备份文件,一会再说。
CodeV2:是对应的TFS上的代码路径
publish:对应各个项目的发布文件地址。上边提到的publishProfile中的目标路径就是这个。

各个文件夹下,我也是按照项目划分 Demo1/Demo2/Demo3…

Jenkins插件手动更新

Jenkins可能受墙的影响更新起来出错概率非常高,所以建议出错了直接手动下载。

下载地址是:http://updates.jenkins-ci.org/download/plugins/

这个列表里有所有插件的安装包,先找到wiki看下插件的依赖,下载的时候把依赖也都下载一遍。这里有个坑是permalink to the latest这个链接下载来的有时候不是最新的,所以最好是手动点最新的!

下载了文件之后,保存到Jekins目录下的plugins,然后重启Jenkins即可。

重启指令是: http://你的Jekins地址/restart

另外文章开头和结尾的截图都是Jenkins的BlueOcean插件,这是它的新UI,老的真的有点丑啊。
这个插件不成功就直接手动吧,安装了好多回,累!

总结

以上就是整个Jenkins自动化构建的研究,建议每个步骤单独分解成job配置run成功之后再整合,否则每次跑一遍狠费时。这也是反复强调的一个workspace,合理文件夹存放的好处之一,否则都没法单独拆分成job来测试。文章中有任何问题,欢迎探讨指正。

最后来一张success:

Java:String和Date、Timestamp之间的转换

一、String与Date(java.util.Date)互转

     1.1 String -> Date

Java代码  收藏代码

  1. String dateStr = “2010/05/04 12:34:23”;

  2.         Date date = new Date();

  3.         DateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”);

  4.         try {

  5.             date = sdf.parse(dateStr);  

  6.             System.out.println(date.toString());  

  7.         } catch (Exception e) {

  8.             e.printStackTrace();  

  9.         }

  

  1.2 Date -> String

   日期向字符串转换,可以设置任意的转换格式format

Java代码  收藏代码

  1. String dateStr = “”;

  2.         Date date = new Date();

  3.         DateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”);

  4.         DateFormat sdf2 = new SimpleDateFormat(“yyyy-MM-dd HH/mm/ss”);

  5.         try {

  6.             dateStr = sdf.format(date);  

  7.             System.out.println(dateStr);  

  8.             dateStr = sdf2.format(date);  

  9.             System.out.println(dateStr);  

  10.         } catch (Exception e) {

  11.             e.printStackTrace();  

  12.         }

  

 二、String与Timestamp互转

   2.1 String ->Timestamp

   使用Timestamp的valueOf()方法

Java代码  收藏代码

  1. Timestamp ts = new Timestamp(System.currentTimeMillis());
  2.         String tsStr = “2011-05-09 11:49:45”;
  3.         try {
  4.             ts = Timestamp.valueOf(tsStr);  
  5.             System.out.println(ts);  
  6.         } catch (Exception e) {
  7.             e.printStackTrace();  
  8.         }

  

   注:String的类型必须形如: yyyy-mm-dd hh:mm:ss[.f...] 这样的格式,中括号表示可选,否则报错!!!

    如果String为其他格式,可考虑重新解析下字符串,再重组~~

    2.2 Timestamp -> String

  使用Timestamp的toString()方法或者借用DateFormat

Java代码  收藏代码

  1. Timestamp ts = new Timestamp(System.currentTimeMillis());

  2.         String tsStr = “”;

  3.         DateFormat sdf = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss”);

  4.         try {

  5.             tsStr = sdf.format(ts);  

  6.             System.out.println(tsStr);  

  7.             tsStr = ts.toString();  

  8.             System.out.println(tsStr);  

  9.         } catch (Exception e) {

  10.             e.printStackTrace();  

  11.         }

  

 很容易能够看出来,方法一的优势在于可以灵活的设置字符串的形式。

三、Date( java.util.Date )和Timestamp互转

  声明:查API可知,Date和Timesta是父子类关系

  3.1 Timestamp -> Date

Java代码  收藏代码

  1. Timestamp ts = new Timestamp(System.currentTimeMillis());
  2.         Date date = new Date();
  3.         try {
  4.             date = ts;  
  5.             System.out.println(date);  
  6.         } catch (Exception e) {
  7.             e.printStackTrace();  
  8.         }

  

 很简单,但是此刻date对象指向的实体却是一个Timestamp,即date拥有Date类的方法,但被覆盖的方法的执行实体在Timestamp中。

   3.2 Date -> Timestamp

   父类不能直接向子类转化,可借助中间的String~~~~

   注:使用以下方式更简洁

   Timestamp ts = new Timestamp(date.getTime());

K3s 集群 helm 化改造之单例 minio - 流雨声 - 博客园

Excerpt

背景概况 MinIO 是全球领先的对象存储先锋,以 Apache License v2.0 发布的对象存储服务器,是为云应用和虚拟机而设计的分布式对象存储服务器。在标准硬件上,读/写速度上高达183GB/s和171GB/s。它与 Amazon S3 云存储服务兼容。 它最适用于存储非结构化数据,如照


背景概况

MinIO 是全球领先的对象存储先锋,以 Apache License v2.0 发布的对象存储服务器,是为云应用和虚拟机而设计的分布式对象存储服务器。在标准硬件上,读/写速度上高达183GB/s和171GB/s。它与 Amazon S3 云存储服务兼容。 它最适用于存储非结构化数据,如照片、视频、日志文件、备份和容器/虚拟机映像。 对象的大小可以从几KB 到最大5TB。

  • 对象存储,兼容Amazon S3协议
  • 安装运维相对简单,开箱即用
  • 后端除了本地文件系统,还支持多种存储系统,目前已经包括 OSS
  • 原生支持bucket事件通知机制
  • 可通过多节点集群方式,支持一定的高可用和数据容灾
  • 有WEB管理界面和CLI,可以用于测试或者管理
  • 公开bucket中的数据可以直接通过HTTP获取

Minio 资源

awk

1
2
<code-line></code-line>
<code-line></code-line>cd <span>/home/</span>work<span>/minio-demo/</span>
  • minio-deployment.yaml

yaml

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
<code-line></code-line><span>apiVersion:</span> <span>apps/v1</span>
<code-line></code-line><span>kind:</span> <span>Deployment</span>
<code-line></code-line><span>metadata:</span>
<code-line></code-line>
<code-line></code-line> <span>name:</span> <span>minio-deployment</span>
<code-line></code-line><span>spec:</span>
<code-line></code-line> <span>strategy:</span>
<code-line></code-line> <span>type:</span> <span>Recreate</span>
<code-line></code-line> <span>selector:</span>
<code-line></code-line> <span>matchLabels:</span>
<code-line></code-line> <span>app:</span> <span>minio</span>
<code-line></code-line> <span>template:</span>
<code-line></code-line> <span>metadata:</span>
<code-line></code-line> <span>labels:</span>
<code-line></code-line>
<code-line></code-line> <span>app:</span> <span>minio</span>
<code-line></code-line> <span>spec:</span>
<code-line></code-line>
<code-line></code-line> <span>volumes:</span>
<code-line></code-line> <span>-</span> <span>name:</span> <span>storage</span>
<code-line></code-line> <span>persistentVolumeClaim:</span>
<code-line></code-line>
<code-line></code-line> <span>claimName:</span> <span>minio-pv-claim</span>
<code-line></code-line> <span>containers:</span>
<code-line></code-line> <span>-</span> <span>name:</span> <span>minio</span>
<code-line></code-line>
<code-line></code-line> <span>image:</span> <span>minio/minio</span>
<code-line></code-line> <span>args:</span>
<code-line></code-line> <span>-</span> <span>server</span>
<code-line></code-line> <span>-</span> <span>/storage</span>
<code-line></code-line> <span>env:</span>
<code-line></code-line>
<code-line></code-line> <span>-</span> <span>name:</span> <span>MINIO_ACCESS_KEY</span>
<code-line></code-line> <span>value:</span> <span>"admin123"</span>
<code-line></code-line> <span>-</span> <span>name:</span> <span>MINIO_SECRET_KEY</span>
<code-line></code-line> <span>value:</span> <span>"admin123"</span>
<code-line></code-line> <span>ports:</span>
<code-line></code-line> <span>-</span> <span>containerPort:</span> <span>9000</span>
<code-line></code-line>
<code-line></code-line> <span>volumeMounts:</span>
<code-line></code-line> <span>-</span> <span>name:</span> <span>storage</span>
<code-line></code-line> <span>mountPath:</span> <span>"/storage"</span>
<code-line></code-line>
  • minio-pv.yaml

dts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<code-line></code-line><span>apiVersion:</span> v1
<code-line></code-line><span>kind:</span> PersistentVolume
<code-line></code-line><span>metadata:</span>
<code-line></code-line><span> labels:</span>
<code-line></code-line><span> app:</span> minio
<code-line></code-line><span> release:</span> minio
<code-line></code-line><span> name:</span> minio
<code-line></code-line><span> namespace:</span> default
<code-line></code-line><span>spec:</span>
<code-line></code-line><span> accessModes:</span>
<code-line></code-line> - ReadWriteOnce
<code-line></code-line><span> capacity:</span>
<code-line></code-line><span> storage:</span> <span>20</span>Gi
<code-line></code-line><span> volumeMode:</span> Filesystem
<code-line></code-line><span> hostPath:</span>
<code-line></code-line><span> path:</span> <span>/mnt/</span>minio
  • minio-pvc.yaml

properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code-line></code-line><span>apiVersion</span>: <span>v1</span>
<code-line></code-line><span>kind</span>: <span>PersistentVolumeClaim</span>
<code-line></code-line><span>metadata</span>:<span></span>
<code-line></code-line>
<code-line></code-line> <span>name</span>: <span>minio-pv-claim</span>
<code-line></code-line> <span>labels</span>:<span></span>
<code-line></code-line> <span>app</span>: <span>minio-storage-claim</span>
<code-line></code-line><span>spec</span>:<span></span>
<code-line></code-line>
<code-line></code-line> <span>accessModes</span>:<span></span>
<code-line></code-line> <span>-</span> <span>ReadWriteOnce</span>
<code-line></code-line> <span>resources</span>:<span></span>
<code-line></code-line>
<code-line></code-line> <span>requests</span>:<span></span>
<code-line></code-line> <span>storage</span>: <span>20Gi</span>
<code-line></code-line>
<code-line></code-line>
  • minio-svc.yaml

yaml

1
2
3
4
5
6
7
8
9
10
11
12
<code-line></code-line><span>apiVersion:</span> <span>v1</span>
<code-line></code-line><span>kind:</span> <span>Service</span>
<code-line></code-line><span>metadata:</span>
<code-line></code-line> <span>name:</span> <span>minio-service</span>
<code-line></code-line><span>spec:</span>
<code-line></code-line> <span>type:</span> <span>NodePort</span>
<code-line></code-line> <span>ports:</span>
<code-line></code-line> <span>-</span> <span>port:</span> <span>9000</span>
<code-line></code-line> <span>targetPort:</span> <span>9000</span>
<code-line></code-line> <span>protocol:</span> <span>TCP</span>
<code-line></code-line> <span>selector:</span>
<code-line></code-line> <span>app:</span> <span>minio</span>
  • minio-ingress.yaml

yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code-line></code-line><span>apiVersion:</span> <span>extensions/v1beta1</span>
<code-line></code-line><span>kind:</span> <span>Ingress</span>
<code-line></code-line><span>metadata:</span>
<code-line></code-line> <span>name:</span> <span>traefik-minio</span>
<code-line></code-line> <span>namespace:</span> <span>default</span>
<code-line></code-line> <span>annotations:</span>
<code-line></code-line> <span>kubernetes.io/ingress.class:</span> <span>traefik</span>
<code-line></code-line><span>spec:</span>
<code-line></code-line> <span>rules:</span>
<code-line></code-line> <span>-</span> <span>host:</span> <span>traefik.minio-minio-standalone.io</span>
<code-line></code-line> <span>http:</span>
<code-line></code-line> <span>paths:</span>
<code-line></code-line> <span>-</span> <span>backend:</span>
<code-line></code-line> <span>serviceName:</span> <span>minio-service</span>
<code-line></code-line> <span>servicePort:</span> <span>9000</span>

资源创建过程:

powershell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<code-line></code-line>
<code-line></code-line>kubectl apply <span>-f</span> minio<span>-pv</span>.yaml
<code-line></code-line>
<code-line></code-line>
<code-line></code-line>kubectl apply <span>-f</span> minio<span>-pv</span>.yaml
<code-line></code-line>
<code-line></code-line>
<code-line></code-line>kubectl apply <span>-f</span> minio<span>-deployment</span>.yaml
<code-line></code-line>
<code-line></code-line>
<code-line></code-line>kubectl apply <span>-f</span> minio<span>-svc</span>.yaml
<code-line></code-line>
<code-line></code-line>
<code-line></code-line>kubectl apply <span>-f</span> minio<span>-ingress</span>.yaml

Helm 改造

资源改造目录如下:

reasonml

1
2
3
4
5
6
7
8
9
10
11
12
13
<code-line></code-line><span>[<span>root</span>@<span>vm3290</span> <span>helm</span>]</span># tree minio-standalone/
<code-line></code-line>minio-standalone/
<code-line></code-line>├── <span><span><span>Chart</span>.</span></span>yaml
<code-line></code-line>├── <span><span><span>README</span>.</span></span>md
<code-line></code-line>├── templates
<code-line></code-line>│&nbsp;&nbsp; ├── minio-deployment.yaml
<code-line></code-line>│&nbsp;&nbsp; ├── minio-ingress.yaml
<code-line></code-line>│&nbsp;&nbsp; ├── minio-pvc.yaml
<code-line></code-line>│&nbsp;&nbsp; ├── minio-pv.yaml
<code-line></code-line>│&nbsp;&nbsp; └── minio-svc.yaml
<code-line></code-line>└── values.yaml
<code-line></code-line>
<code-line></code-line><span>1</span> directory, <span>8</span> files

定义 values.yaml 内容如下:

dts

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
<code-line></code-line><span>replicas:</span> <span>1</span>
<code-line></code-line><span>strategy:</span> Recreate
<code-line></code-line><span>resources:</span>
<code-line></code-line><span> limits:</span>
<code-line></code-line><span> cpu:</span> <span>"1"</span>
<code-line></code-line><span> memory:</span> <span>1</span>Gi
<code-line></code-line><span> requests:</span>
<code-line></code-line><span> cpu:</span> <span>200</span>m
<code-line></code-line><span> memory:</span> <span>200</span>Mi
<code-line></code-line><span>
<code-line></code-line>pv:</span>
<code-line></code-line><span> name:</span> minio
<code-line></code-line><span> accessModes:</span> ReadWriteOnce
<code-line></code-line><span> storage:</span> <span>20</span>Gi
<code-line></code-line><span>
<code-line></code-line>pvc:</span>
<code-line></code-line><span> name:</span> minio-pv-claim
<code-line></code-line><span>
<code-line></code-line>service:</span>
<code-line></code-line><span> name:</span> minio-service
<code-line></code-line><span>
<code-line></code-line>ingress:</span>
<code-line></code-line><span> name:</span> minio-ingress
<code-line></code-line><span> host:</span> traefik.minio-minio-standalone.io
<code-line></code-line>
<code-line></code-line><span># 请不要轻易修改此配置</span>
<code-line></code-line><span>global:</span>
<code-line></code-line><span> namespace:</span> minio-ns
<code-line></code-line><span> minio:</span>
<code-line></code-line><span> name:</span> minio-ds
<code-line></code-line><span> image:</span> minio/minio
<code-line></code-line><span> tag:</span> latest
<code-line></code-line><span> port:</span> <span>9000</span>

项目部署

vim

1
2
3
4
5
6
7
8
<code-line></code-line><span>1</span>. 安装
<code-line></code-line>helm install -n <span>&lt;YOUR-NAMESPACE&gt;</span> standalone standalone
<code-line></code-line>
<code-line></code-line><span>2</span>. 更新
<code-line></code-line>helm upgrade -n <span>&lt;YOUR-NAMESPACE&gt;</span> standalone standalone
<code-line></code-line>
<code-line></code-line><span>3</span>. 卸载
<code-line></code-line>helm <span>delete</span> -n <span>&lt;YOUR-NAMESPACE&gt;</span> standalone

minio 部署资源查看

配置 hosts 文件:

浏览器打开访问:

拓展阅读

本次改造的 minio 是单实例节点副本的 helm 化改造,主要用于机器资源不足,没有进行全盘虚拟化的场景下的资源调度管理上的应用部署,针对应用的场景不同,所以需要结合不同的项目需求进行设计。minio 可以支持单实例副本,同时也支持最小的四节点集群模式(所以在资源较少时建议采用单副本的minio,在集群中可以调度的机器节点资源大于等于4时,minio的集群模式才可以正常使用,后续将会开展集群模式的改造部署)

__EOF__