Zhuang's Diary

言之有物,持之以恒

Kuboard 是 Kubernetes 的一款图形化管理界面。参考如下链接:https://kuboard.cn/install/install-dashboard.html 安装kuboard

  1. kuboard 首页
  1. Deployment 视图页面

体验结论:

  1. kuboard 比 Kubernetes Dashboard(Kubernetes 的官方 Web UI)要友好许多,并且 Kubernetes Dashboard 需要在操作机中具备 Kubectl;
  2. wayne 对用户的权限管理会优秀一些。但是经过docker-compose实验,启动为dev版本,未成功打开页面
  3. rancherhttps://github.com/rancher/)Rancher为您提供“Kubernetes即服务(Kubernetes-as-a-Service)”。在国内北京、上海、深圳、沈阳均有办公地点,汉语支持较好。推荐使用。

SeaweedFS 是开源的,简单的,高伸缩性的分布式文件系统。SeaweedFS 作为支持全 POSIX 文件系统语义替代,Seaweed-FS 选择仅实现 key-file 的映射,类似 “NoSQL”,也可以说是 “NoFS”。

SeaweedFS 仅花费 40 字节的硬盘来存储每个文件的元数据。

GlusterFS, Ceph相比较

System File Meta File Content Read POSIX REST API Optimized for small files
SeaweedFS lookup volume id, cacheable O(1) disk seek Yes Yes
SeaweedFS Filer Linearly Scalable, Customizable O(1) disk seek FUSE Yes Yes
GlusterFS hashing FUSE, NFS
Ceph hashing + rules FUSE Yes
MooseFS in memory FUSE No

体验seaweedfs

参考==>https://hub.docker.com/r/chrislusf/seaweedfs

  1. 拉取 docker-compose 文件,wget https://raw.githubusercontent.com/chrislusf/seaweedfs/master/docker/seaweedfs-compose.yml
  2. 启动,docker-compose -f seaweedfs-compose.yml -p seaweedfs up

参考 => https://github.com/chrislusf/seaweedfs/wiki/Volume-Server-API

1
2
3
4
5
6
➜  seaweedfs: curl http://localhost:9333/dir/assign
{"fid":"7,01248b7b86","url":"172.18.0.3:8080","publicUrl":"172.18.0.3:8080","count":1}%
➜ seaweedfs: curl -F file=@/home/will/documents/zaq12wsxcde3--de4064dc15870163a9aab589a1ccf7900dd68ef4 http://127.0.0.1:8080/7,01248b7b86
{"name":"zaq12wsxcde3--de4064dc15870163a9aab589a1ccf7900dd68ef4","size":489,"eTag":"468de108"}%
➜ seaweedfs: curl http://localhost:9333/dir/lookup\?volumeId\=7
{"volumeId":"7","locations":[{"url":"172.18.0.3:8080","publicUrl":"172.18.0.3:8080"}]}%
  1. curl http://localhost:9333/dir/assign 获取fid
  2. curl -F file=@/home/will/documents/zaq12wsxcde3--de4064dc15870163a9aab589a1ccf7900dd68ef4 http://127.0.0.1:8080/7,01248b7b86 上传文件
  3. curl http://localhost:9333/dir/lookup\?volumeId\=7 查询
  4. 在浏览器中打开 http://172.18.0.3:8080/7,01248b7b86,查看文档
  5. master 的url:http://127.0.0.1:9333/
  6. volume的url:http://172.18.0.3:8080/ui/index.html

Chainlink是一个去中心化的预言机,但是你同样可以在私有环境中使用Chainlink,让它成为一个为私有链或联盟链服务的中心化的预言机系统。只要你的私有链环境支持完整的EVM环境,就可以利用Chainlink的开源实现,为自己的系统搭建一个具有广泛适配能力的预言机系统。

准备工作

私有链/联盟链(需支持EVM,开放WebSocket端口)

由于目前Chainlink的合约系统都是在以太坊网络上的虚拟环境中运行的,所以需要您的私有链/联盟链支持EVM环境以及Solidity合约编程语言。

用于部署合约的账户,账户需持有足够数量的Native Token(原生代币)

一般来说,向区块链提交事务,不管是创建合约还是普通转账,都需要事务发起账户提供一定的手续费,所以需要一个持有原生代币的账户,用于部署合约和节点提交事务。如果您的私有链提交事务不需要手续费,那仅需要一个账户就可以。(具体请根据您的私有链的配置操作)

一台服务器用于部署Chainlink节点

Chainlink节点相当于区块链在真实世界中的代理,它可以接收链上的数据请求,并获取到链上所需要的数据,通过事务提交给链上。所以需要一台服务器来建立Chainlink节点服务,服务器配置不需要很高,但是需要和私有链环境保持良好的网络通讯。

之前的Chainlink节点版本默认使用Sqlite作为存储数据库,但是随着功能越来越完善,性能要求也越来越高,从0.8.0版本起,Chainlink节点要求必须使用postgres作为存储数据库,所以我们需要搭建一个postgres数据库。这个数据库可以和节点位于同一台服务器上,也可以位于不同的服务器上。您也可以同时搭建两个数据库作为备份。

(可选)准备truffle suite和npm或WebIDE Remix等开发部署工具

您可以选择Chainlink提供的truffle box来编写和部署测试合约

1
truffle unbox smartcontractkit/box

也可以在已有的项目中添加Chainlink开发库

1
2
truffle init
npm install @chainlink/contracts --save

同样的,您也可以使用可视化的Web IDE Remix来进行开发,使用起来更加方便,也无需配置本地开发环境。在使用Remix时,可以搭配MetaMask来进行转账和事务操作。

https://remix.ethereum.org

https://metamask.io

实施步骤

0.搭建 EVM 私链

LinkToken.sol

所有Chainlink的功能实现和数据流转都是围绕LINK token来实现的。这里的remix中默认是https url,如果访问vmware中的linux server,建议在remix中使用 http url

2.部署Chainlink节点

2.1 安装docker、postgres (建议postgres也在docker中安装)

2.2 创建环境变量配置文件

1
2
3
mkdir ~/.chainlink
cd ~/.chainlink
vim .env
1
2
3
4
5
6
7
8
9
10
11
12
ROOT=/chainlink
LOG_LEVEL=debug
ETH_CHAIN_ID=1337
MIN_OUTGOING_CONFIRMATIONS=0
MIN_INCOMING_CONFIRMATIONS=0
LINK_CONTRACT_ADDRESS=0x9E05B78ea853a4B093694645561c4BFc953A6f62
CHAINLINK_TLS_PORT=0
SECURE_COOKIES=false
ALLOW_ORIGINS=*
ETH_URL=ws://localhost:8546
DATABASE_URL=postgresql://postgres:123456@localhost:5432/chainlink?sslmode=disable
DATABASE_TIMEOUT=0

其中ETH_URL是私有链的RPC接口,必须是WebSocket接口,可以是ws也可以是wss

LINK_CONTRACT_ADDRESS 是刚刚部署的Link token 地址。

2.3 通过docker启动

1
2
3
4
5
cd ~/.chainlink
# 启动postgres
docker run --name postgres_chainlink -e POSTGRES_PASSWORD=123456 -v /home/will/documents/chainlink:/var/lib/postgresql/data -p 5432:5432 -d postgres:11.5-alpine
# 启动chainlink节点
docker run --net host -p 6688:6688 -v ~/.chainlink:/chainlink -it --env-file=.env smartcontract/chainlink:0.7.8 local n

其中--net host 是为了让通过docker启动的Chainlink节点可以访问到宿主记得网络,否则上面配置文件中的localhost都是不可访问的。也可以通过其他更加安全的docker网络配置完成同样功能。

0.7.8是Chainlink的release版本号,不写的话默认是latest。

2.4 记录节点管理密码

首次启动成功后,首先会要求输入一个密码,这个密码是Chainlink节点账户的私钥密码,可以用来控制Chainlink节点账户,必须牢记,否则节点账户所持有的资金无法取出。

然后会要求输入一对用户名和密码,这是Chainlink管理界面的的用户名和密码,在通过 http: //chainlink_ip:6688 访问管理端界面时会要求输入这对用户名和密码。

2.5 创建Job

https://docs.chain.link/docs/fulfilling-requests#section-add-jobs-to-the-node

创建不同数据类型的 Job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"initiators": [
{
"type": "runlog"
}
],
"tasks": [
{
"type": "httpget"
},
{
"type": "jsonparse"
},
{
"type": "ethbytes32"
},
{
"type": "ethtx"
}
]
}

2.6 向Chainlink节点(ACCOUNT_ADDRESS)转入一定数量的Native token用于提交事务

比如:如果是以太坊公网,需要转入ETH,以作为交易的gas费用。

节点地址可以在管理页面的configure页面下找到,账户资金仅用于提交transaction。

3.部署oracle contract

1
2
3
pragma solidity 0.4.24;

import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/Oracle.sol";

调用合约中的setFulfillPermission方法,传递参数为(ACCOUNT_ADDRESS,true),ACCOUNT_ADDRESS是ChainLink Operator。

4.编写部署用户合约

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
pragma solidity 0.4.24;

import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/ChainlinkClient.sol";


contract GetTemperature is ChainlinkClient {
LinkTokenInterface internal LinkToken;


//string constant url = "https://www.random.org/integers/?num=1&min=1&max=6&col=1&base=10&format=plain&rnd=new";
string constant url = "https://api.seniverse.com/v3/weather/now.json?key=S9g8Ize9pNyUZ_BOP&location=shanghai&language=zh-Hans&unit=c";
address constant oracleAddress = 0xa6126AD8B8307C6e1b668F486BEA155e814FA22d;
bytes32 constant JobId = "d91130d49daf46aaa591bcbce6d59b72";
address constant linkAddress = 0x9E05B78ea853a4B093694645561c4BFc953A6f62;

constructor() public {
setChainlinkToken(linkAddress);
setChainlinkOracle(oracleAddress);
LinkToken = LinkTokenInterface(linkAddress);
}

string public temperature;

function getData() public {

// 发起Chainlink请求
requestTemperature(JobId);

}


function requestTemperature(bytes32 _jobId) public returns (bytes32 requestId) {

Chainlink.Request memory req = buildChainlinkRequest(_jobId, this, this.fulfillTemperature.selector);

req.add("get", url);
req.add("path", "results.0.now.temperature");

requestId = sendChainlinkRequest(req, 1 * LINK);

return requestId;
}


function fulfillTemperature(bytes32 _requestId, bytes32 _temp)
public recordChainlinkFulfillment(_requestId)
{
temperature = bytes32ToString(_temp);
//data = _temp;
}


function bytes32ToString(bytes32 x) private pure returns (string) {
bytes memory bytesString = new bytes(32);
uint charCount = 0;
for (uint j = 0; j < 32; j++) {
byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed);
}

}

其中 linkAddress、OracleAddress、JobId 需要根据上面的配置结果填写,或作为参数在调用时传入。

向部署好的用户合约地址转入Link token,用户在发起请求是支付给节点的费用。上述部署的Link Token为18位精度,建议使用 transfer 方法转移 Link token 2000,000000000000000000

发起请求获取数据,使用getData方法获取天气数据。

准备虚拟服务器

  1. 在vmware上安装centos7.7 server

  2. vmware工具栏,虚拟机->管理->克隆,制作k8s-master

  3. vmware工具栏,虚拟机->管理->克隆,制作k8s-worker01

  4. vmware工具栏,虚拟机->管理->克隆,制作k8s-worker02

  5. 于k8s-master内,/etc/sysconfig/network-scripts/ifcfg-ens33,将server编辑为静态ip,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #######################################
    ### depends on vm network settings ####
    #######################################
    BOOTPROTO=static
    IPADDR=192.168.180.10
    NETMASK=255.255.255.0
    GATEWAY=192.168.180.2
    DNS1=192.168.180.2
    ONBOOT=yes

    其中,192.168.180.2来自vmware工具栏,编辑->虚拟网络编辑器->NAT设置->子网IP

    重启server网卡如下:

    1
    2
    nmcli c reload ens33
    nmcli c up ens33

    从windows登录k8s-master,登录IP为192.168.180.10

  6. 于k8s-worker01内,/etc/sysconfig/network-scripts,将server编辑为静态ip,IPADDR=192.168.180.11

    重启server网卡如上

  7. 于k8s-worker01内,/etc/sysconfig/network-scripts,将server编辑为静态ip,IPADDR=192.168.180.12

    重启server网卡如上

  8. optional,检查vmware,编辑->虚拟网络编辑器 中的VMnet8的连通状态,如果有需要,在windows10->设置->更改适配器选项,重新启动VMnet8网卡

从winscp端登录:

最后,按照 kuboard -> 安装 -> 安装 kubernetes 手册完成kubernetes安装。

我相信每个人都会想,每次更新完代码,更新完配置文件后,就直接这么 ctrl+c 真的没问题吗,ctrl+c到底做了些什么事情呢?

本文我们讨论 ctrl+c 背后的信号以及如何优雅的重启服务,以及对 HTTP 服务进行热更新。

ctrl + c

在终端执行特定的组合键可以使系统发送特定的信号给此进程,完成一系列的动作

命令 信号 含义
ctrl + c SIGINT 强制进程结束
ctrl + z SIGTSTP 任务中断,进程挂起
ctrl + \ SIGQUIT 进程结束 和 dump core
ctrl + d EOF
常用于重启、重新加载进程 SIGHUP 若程序中没有捕捉该信号,当收到该信号时,进程就会退出
SIGPIPE 在进程往一个已经关闭的管道写数据时会产生

因此在我们执行ctrl + c关闭服务端时,会强制进程结束,导致正在访问的用户等出现问题

常见的 kill -9 pid 会发送 SIGKILL 信号给进程,也是类似的结果。

信号

本段中出现信号是什么呢?

信号是 Unix 、类 Unix 以及其他 POSIX 兼容的操作系统中进程间通讯的一种有限制的方式。

它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

所有信号

从go语言 go root SDK中 zerrors_linux_amd64.go 来看,关于信号有如下常量:

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
SIGABRT   
SIGALRM
SIGBUS
SIGCHLD
SIGCLD
SIGCONT
SIGFPE
SIGHUP
SIGILL
SIGINT
SIGIO
SIGIOT
SIGKILL
SIGPIPE
SIGPOLL
SIGPROF
SIGPWR
SIGQUIT
SIGSEGV
SIGSTKFLT
SIGSTOP
SIGSYS
SIGTERM
SIGTRAP
SIGTSTP
SIGTTIN
SIGTTOU
SIGUNUSED
SIGURG
SIGUSR1
SIGUSR2
SIGVTALRM
SIGWINCH
SIGXCPU
SIGXFSZ

怎样算优雅

优雅地退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TrapSignal catches the SIGTERM/SIGINT/SIGKILL and executes cb function. 
// After that it exits with code 0.
func TrapSignal(cb func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
go func() {
for sig := range c {
fmt.Sprintf("captured %v, exiting...", sig)
if cb != nil {
cb()
}
os.Exit(0)
}
}()
}

在接收退出信号SIGTERM/SIGINT/SIGKILL之后,系统退出os.Exit(0)之前,执行cb() callback函数。具体执行方法建议如下:

1
2
3
4
5
6
7
8
9
10
11
func Cmd(ctx *cli.Context) error {
// program of business logic
start()

TrapSignal(logger, func() {
// program of business exit here
stop()
})

return nil
}

gin退出

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
engine := gin.Default()
engine.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
TrapSignal(func() {
fmt.Println("before exit.")
})

engine.Run()
}