服务调用示意

dapr调用示意

ZidVEq

sidecar 之间的通信都是 gRPC (3、6)
application与sidecar之间的通信是http/grpc (1、7、4、5)

  1. 服务 A 对服务 B 发起HTTP/gRPC的调用。
  2. Dapr使用服务注册发现平台的名称解析组件发现服务B的位置。(例如:mDNS、consul等)
  3. Dapr 将消息转发至服务 B的 Dapr 边车
    注: Dapr 边车之间的所有调用考虑到性能都优先使用 gRPC。 仅服务与 Dapr 边车之间的调用可以是 HTTP 或 gRPC
  4. 服务 B的 Dapr 边车将请求转发至服务 B 上的特定端点 (或方法) 。 服务 B 随后运行其业务逻辑代码。
  5. 服务 B 发送响应给服务 A。 响应将转至服务 B 的边车。
  6. Dapr 将消息转发至服务 A 的 Dapr 边车。
  7. 服务 A 接收响应。

python和node服务示例

ipdCVT

  1. Node.js应用程序有一个app IDnodeapp的Dapr应用程序。 当python应用程序通过 POST http://localhost:3500/v1.0/invoke/nodeapp/method/neworder 调用 Node.js 应用程序的 neworder方法时, 首先会到达python app的本地dapr sidecar。
  2. Dapr 使用本地机器运行的名称解析组件(在这种情况下自动运行的 mDNS),发现 Node.js 应用的位置。
  3. Dapr 使用刚刚收到的位置将请求转发到 Node.js 应用的 sidecar。
  4. Node.js 应用的 sidecar 将请求转发到 Node.js 应用程序。 Node.js 应用执行其业务逻辑,记录收到的消息,然后将订单 ID 存储到 Redis (未在图表中显示)中
  5. Node.js应 用程序通过 Node.js sidecar 向 Python 应用程序发送一个响应。
  6. Dapr 转发响应到 Python 的 Dapr sidecar
  7. Python 应用程序收到响应。

API 和端口

Dapr runtime 对外提供两个 API,分别是 Dapr HTTP API 和 Dapr gRPC API。另外两个 dapr runtime 之间的通讯 (Dapr internal API) 固定用 gRPC 协议。

两个 Dapr API 对外暴露的端口

  • 3500: HTTP 端口,可以通过命令行参数 dapr-http-port 设置
  • 50001: gRPC 端口,可以通过命令行参数 dapr-grpc-port 设置
1
dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 3500 --dapr-grpc-port 50001 node app.js

Dapr internal API 是内部端口,比较特殊,没有固定的默认值,而是取任意随机可用端口。也可以通过命令行参数 dapr-internal-grpc-port 设置。

为了向服务器端的应用发送请求,dapr 需要获知应用在哪个端口监听并处理请求,这个信息通过命令行参数 app-port 设置。Dapr 的示例中一般喜欢用 3000 端口。

实践

我们使用官方的教程来实验一下

启动服务

我们先开启node的订单服务

1
2
3
cd ./hello-world/node
npm install
dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 3500 node app.js
1
ℹ️  Starting Dapr with id nodeapp. HTTP Port: 3500. gRPC Port: 54626

服务解析

启动后,我们先看看订单服务有什么操作
创建订单:/neworder

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
app.post('/neworder', async (req, res) => {
const data = req.body.data;
const orderId = data.orderId;
console.log("Got a new order! Order ID: " + orderId);

const state = [{
key: "order",
value: data
}];

try {
const response = await fetch(stateUrl, {
method: "POST",
body: JSON.stringify(state),
headers: {
"Content-Type": "application/json"
}
})
if (!response.ok) {
throw "Failed to persist state.";
}
console.log("Successfully persisted state.");
res.status(200).send();
} catch (error) {
console.log(error);
res.status(500).send({message: error});
}
});

获取订单:/order

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.get('/order', async (_req, res) => {
try {
const response = await fetch(`${stateUrl}/order`)
if (!response.ok) {
throw "Could not get state.";
}
const orders = await response.text();
res.send(orders);
}
catch (error) {
console.log(error);
res.status(500).send({message: error});
}
});

服务调用

http调用方式

yMot68

使用 Dapr cli 调用
1
2
3
dapr invoke --app-id nodeapp --method neworder --data-file sample.json
// sample.json
{"data":{"orderId":"42"}}
1
2
3
4
// 创建订单
dapr invoke --app-id nodeapp --method neworder --data '{"data": { "orderId": "42" } }'
// 获取订单
dapr invoke --app-id nodeapp --method order --verb GET
使用curl调用

通过dapr的Endpoint

1
2
3
4
// 创建订单
curl -XPOST -d @sample.json -H Content-Type:application/json http://localhost:3500/v1.0/invoke/nodeapp/method/neworder
// 获取订单
curl http://localhost:3500/v1.0/invoke/nodeapp/method/order

通过Node程序自己的Endpoint,这样不通过Dapr Sidecar。

1
curl -XPOST -d @sample.json -H "Content-Type:application/json" http://localhost:3000/neworder
使用Postman调用

6iLdya
0cTGFL

使用SDK

默认HTTP

1
2
3
4
5
6
7
8
9
10
11
12
13
import { DaprClient } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort);

const serviceAppId = "nodeapp";
const serviceMethod = "neworder";

// POST Request
const response = await client.invoker.invoke(serviceAppId , serviceMethod , HttpMethod.POST, { data: {"orderId":"42"} });

const serviceMethod2 = "order";
// GET Request
const response = await client.invoker.invoke(serviceAppId , serviceMethod2 , HttpMethod.GET);

另一个程序语言的服务调用(python)

接下来部署Python的程序。Python也得先装运行环境:

1
2
3
cd ./hello-world/python
sudo apt install python3-pip
dapr run --app-id pythonapp --dapr-http-port 3501 python3 app.py

启动成功了。因为Python自己不提供服务,所以–app-port不用指定。–dapr-http-port是3501,这是自己的Sidecar用的端口,不能跟别人的重了。再看看刚才Node的窗口,不停的有新的Request过来,就是Python程序来的每隔一秒的Request。
最后看一下dapr list的结果:

1
2
3
APP ID     HTTP PORT  GRPC PORT  APP PORT  COMMAND         AGE  CREATED              PID
nodeapp 3500 35485 3000 node app.js 41m 2020-12-27 00:54.54 18395
pythonapp 40175 33349 0 python3 app.py 1m 2020-12-27 01:36.27 31185

GRPC调用方式

ryUFBq

GRPC调用方式,在application得起一个GRPC服务,然后通过dapr调用application的GRPC服务

使用SDK
1
2
3
4
5
6
7
import { DaprClient, CommunicationProtocol } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort, CommunicationProtocol.GRPC);

// Create a Proxy that allows us to use our gRPC code

const clientProxy = await client.proxy.create<GreeterClient>(GreeterClient);

前提

本次我们将实践dapr的mDns和consul的负载均衡

服务示例

  1. service1是一个获取ip的nodejs项目,分别使用dapr部署在dapr1dapr2的虚拟机上
  2. service2是另外一个服务,调用server1的getIp服务,部署在dapr3的虚拟机上

我们的目标是通过postman访问service2,调用部署在dapr1、dapr2的getIp服务,看看是否能达到负载均衡

service1核心代码:

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const express = require('express')
const os = require('os');
const app = express()
const port = 3000

function getIpAddress() {
var interfaces=os.networkInterfaces()

for (var dev in interfaces) {
let iface = interfaces[dev]

for (let i = 0; i < iface.length; i++) {
let {family, address, internal} = iface[i]

if (family === 'IPv4' && address !== '127.0.0.1' && !internal) {
return address
}
}
}
}

app.get('/getIp', (req, res) => {
const ipAddress = getIpAddress()
res.send(ipAddress)
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

mDns负载均衡(局域网内)

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

dapr1 192.168.10.201: dapr部署service1
dapr run --dapr-http-port 3600 --app-port 3000 --app-id service1 node index.js


dapr2 192.168.10.202: dapr部署service1
dapr run --dapr-http-port 3600 --app-port 3000 --app-id service1 node index.js


dapr3: 192.168.10.203: dapr部署service2
dapr run --dapr-http-port 3500 --app-id service2

多次访问,可看到达到了负载的效果

1
curl http://192.168.10.203:3500/v1.0/invoke/service1/method/getIp
1
2
3
4
192.168.10.202
192.168.10.201
192.168.10.202
192.168.10.201

consul的负载均衡(夸机房,不需要在同一局域网)

前面的负载我们使用了mDNS,官方还为我们提供了Consul名字解析组件,我们新建一个虚拟机Consul(192.168.10.204)来安装Consul
安装步骤可参考:Consul集群-服务自动发现实践

Q7hLRH

下面修改Dapr1和Dapr2机器上的~/.dapr/config.yaml配置,让其使用Consul来解析名字

1
2
3
4
5
6
7
8
9
10
11
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
spec:
nameResolution:
component: "consul"
configuration:
client:
address: "192.168.10.204:8500"
selfRegister: true

修改Dapr3机器上的~/.dapr/config.yaml配置

1
2
3
4
5
6
7
8
9
10
11
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
spec:
nameResolution:
component: "consul"
configuration:
client:
address: "10.8.99.45:8500"
selfRegister: false

当我们重新启动dapr1和dapr2的service1后,我们看到,已经注册到consul上去了。
fmRPJh

问题

待解决
consul如何自动剔除失效服务

Consul移除失效服务

1
curl -X PUT http://127.0.0.1:8500/v1/agent/service/deregister/{service_id}

Consul移除正常关机nodes节点

1
curl -X PUT http://127.0.0.1:8500/v1//agent/force-leave/{node}

目标

consul集群服务+consul-template+nginx实现nginx反向代理地址的自动更新

  • consul:自动发现、自动更新,为容器提供服务(添加、删除、生命周期)
  • registrator(自动发现+注册到consul-server端)
  • consul-template模板(更新)

核心机制: 后端每更新一个容器,会向registrator进行注册,控制consul完成更新操作,consul会触发consul-template模板进行热更新(reload)

启动Consul集群

启动4个consul,其中consul1 是主节点,consul2、consul3 是子节点。consul4是提供ui服务的。

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
version: '3.5'
services:
consul1:
image: consul:latest
container_name: consul1
restart: always
command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul1
volumes:
- ./consul1/data:/consul/data
- ./consul1/config:/consul/config
consul2:
image: consul:latest
container_name: consul2
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul2
volumes:
- ./consul2/data:/consul/data
- ./consul2/config:/consul/config
consul3:
image: consul:latest
container_name: consul3
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul3
volumes:
- ./consul3/data:/consul/data
- ./consul3/config:/consul/config
consul4:
image: consul:latest
container_name: consul4
restart: always
ports:
- 8500:8500
command: agent -client=0.0.0.0 -retry-join=consul1 -ui -node=client1
volumes:
- ./consul4/data:/consul/data
- ./consul4/config:/consul/config

EVl7kb

  • -server:表示当前使用的server模式;如果没有指定,则表示是client模式。
  • -node:指定当前节点在集群中的名称。
  • -config-dir:指定配置文件路径,定义服务的;路径下面的所有.json结尾的文件都被访问;缺省值为:/consul/config。
  • -data-dir: consul存储数据的目录;缺省值为:/consul/data。
  • -datacenter:数据中心名称,缺省值为dc1。
  • -ui:使用consul自带的web UI界面 。
  • -join:加入到已有的集群中。
  • -enable-script-checks: 检查服务是否处于活动状态,类似开启心跳。
  • -bind: 绑定服务器的ip地址。
  • -client: 客户端可访问ip,缺省值为:“127.0.0.1”,即仅允许环回连接。
  • -bootstrap-expect:在一个datacenter中期望的server节点数目,consul启动时会一直等待直到达到这个数目的server才会引导整个集群。这个参数的值在同一个datacenter的所有server节点上必须保持一致。

consul 端口

端口 说明
TCP/8300 8300端口用于服务器节点。客户端通过该端口RPC协议调用服务端节点
TCP/UDP/8301 8301端口用于单个数据中心所有节点之间的互相通信, 即对LAN池信息的同步。她使得整个数据中心能够自动发现服务器地址,分布式检测节点故障,事件广播(如领导选举事件)
TCP/UDP/8302 8302端口用于单个或多个数据中心之间的服务器节点的信息同步,即对WAN池信息的同步。它针对互联网的高延迟进行了优化,能够实现跨数据中心请求
8500 8500端口基于HTTP协议,用于API接口或者WEB UI访问
8600 8600端口作为DNS服务器,它使得我们可以通过节点名查询节点信息

通过http api 获取集群信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看集群server成员
[root@localhost consul]# curl 127.0.0.1:8500/v1/status/peers
[
"172.23.0.5:8300",
"172.23.0.3:8300",
"172.23.0.4:8300"
]

# 查看集群Raf leader
[root@localhost consul]# curl 127.0.0.1:8500/v1/status/leader
"172.23.0.4:8300"

# 查看注册的所有服务
[root@localhost consul]# curl 127.0.0.1:8500/v1/catalog/services
{"consul":[]}

# 查看nginx服务的信息
[root@localhost consul]# curl 127.0.0.1:8500/v1/catalog/nginx

# 集群节点详细信息
[root@localhost consul]# curl 127.0.0.1:8500/v1/catalog/nodes
[{"ID":"dc6703d1-2324-c388-d5bc-226d7d79e733","Node":"client1","Address":"172.23.0.2","Datacenter":"dc1","TaggedAddresses":{"lan":"172.23.0.2","lan_ipv4":"172.23.0.2","wan":"172.23.0.2","wan_ipv4":"172.23.0.2"},"Meta":{"consul-network-segment":""},"CreateIndex":8,"ModifyIndex":11},{"ID":"f328a0ce-ba8b-b270-f2f0-850f2f762334","Node":"consul1","Address":"172.23.0.4","Datacenter":"dc1","TaggedAddresses":{"lan":"172.23.0.4","lan_ipv4":"172.23.0.4","wan":"172.23.0.4","wan_ipv4":"172.23.0.4"},"Meta":{"consul-network-segment":""},"CreateIndex":5,"ModifyIndex":10},{"ID":"324bef0c-fa7e-ae47-3bcc-6f06e45f8e4b","Node":"consul2","Address":"172.23.0.5","Datacenter":"dc1","TaggedAddresses":{"lan":"172.23.0.5","lan_ipv4":"172.23.0.5","wan":"172.23.0.5","wan_ipv4":"172.23.0.5"},"Meta":{"consul-network-segment":""},"CreateIndex":7,"ModifyIndex":13},{"ID":"d59cbbc9-d1b9-da27-5c9b-3e35eabae824","Node":"consul3","Address":"172.23.0.3","Datacenter":"dc1","TaggedAddresses":{"lan":"172.23.0.3","lan_ipv4":"172.23.0.3","wan":"172.23.0.3","wan_ipv4":"172.23.0.3"},"Meta":{"consul-network-segment":""},"CreateIndex":9,"ModifyIndex":12}]

部署registrator

1
2
3
4
5
6
7
8
9
10
version: "3.5"
services:
registrator:
image: gliderlabs/registrator:latest
container_name: registrator
restart: always
network_mode: host
volumes:
- "/var/run/docker.sock:/tmp/docker.sock"
command: consul://10.8.99.45:8500

consul-template自动加入nginx集群

1
2
3
4
5
cd /opt/
wget https://releases.hashicorp.com/consul-template/0.26.0/consul-template_0.26.0_linux_amd64.zip

# 解压到指定目录,推荐/usr/bin/目录下,可以直接使用
tar -xf consul-template_0.26.0_linux_amd64.zip -C /usr/bin/

配置模板文件

vim /data/template/nginx.ctmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
upstream http_backend {         
{{range service "nginx"}}
server {{.Address}}:{{.Port}};
{{end}}
}

server {
listen 1216; ##监听consul的端口,这是代理端口,consul是作为一个代理服务,访问后端的容器服务
server_name localhost 10.8.99.45; ##监听本地地址,监听代理端的地址
access_log /var/log/nginx/access.log; ##nginx日志目录,如果是编译安装的nginx需要自行创建
index index.html index.php;
location / { ##反向代理的信息,代理的头部信息
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://http_backend; ##跳转到服务器池的地址和端口
}
}

安装nginx

1
yum install -y nignx
1
2
3
4
cat /etc/nginx/nginx.conf
...
include /etc/nginx/conf.d/*.conf;
...

启动consul-template,指定template模板文件及生成路径

1
2
3
# 进入监控状态
[root@localhost ~]# consul-template -consul-addr 10.8.99.45:8500 -template \ "/data/template/nginx.ctmpl:/etc/nginx/conf.d/app.conf:nginx -s reload" \
--log-level=info
  • /data/template/nginx.ctmpl 模板文件
  • /etc/nginx/conf.d/app.conf 生成的配置文件
  • nginx -s reload,重载服务
  • -log-level=info,指定日志级别

查看生成后的文件

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
cat /etc/nginx/conf.d/app.conf

upstream http_backend {

server 10.8.99.45:81;

server 10.8.99.45:82;

server 10.8.99.45:83;

server 10.8.99.45:84;

}
server {
listen 1216;
server_name localhost 10.0.0.14;
access_log /var/log/nginx/access.log;
index index.html index.php;
location / {
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://http_backend;
}
}

由此完成通过consul的实时新增删除服务,然后compose-template可以实现动态增加服务节点到nginx代理的配置文件,这样就形成了服务的自动化增减

上一次我们实践了 Local 模式和Standalone 模式 :Flink多种集群模式部署实践
我们来实践一下推荐生产环境部署的 Flink on yarn模式

这篇贼辛苦,因为涉及到 hadoop和flink的部署,不过收获也颇多。

在实际开发中,使用Flink时,更多的使用方式是Flink On Yarn模式,原因如下:
Yarn的资源可以按需使用,提高集群的资源利用率
Yarn的任务有优先级,根据优先级运行作业
基于Yarn调度系统,能够自动化地处理各个角色的 Failover(容错)

  • JobManager 进程和 TaskManager 进程都由 Yarn NodeManager 监控
  • 如果 JobManager 进程异常退出,则 Yarn ResourceManager 会重新调度 JobManager 到其他机器
  • 如果 TaskManager 进程异常退出,JobManager 会收到消息并重新向 Yarn ResourceManager 申请资源,重新启动 TaskManage

Flink如何和Yarn进行交互?

sh7K78

  • 1.Client上传jar包和配置文件到HDFS集群上

  • 2.Client向Yarn ResourceManager提交任务并申请资源

  • 3.ResourceManager分配Container资源并启动ApplicationMaster,然后AppMaster加载Flink的Jar包和配置构建环境,启动JobManager

    JobManager和ApplicationMaster运行在同一个container上。一旦他们被成功启动,AppMaster就知道JobManager的地址(AM它自己所在的机器)。它就会为TaskManager生成一个新的Flink配置文件(他们就可以连接到JobManager)。这个配置文件也被上传到HDFS上。此外,AppMaster容器也提供了Flink的web服务接口。YARN所分配的所有端口都是临时端口,这允许用户并行执行多个Flink

  • 4.ApplicationMaster向ResourceManager申请工作资源,NodeManager加载Flink的Jar包和配置构建环境并启动TaskManager

  • 5.TaskManager启动后向JobManager发送心跳包,并等待JobManager向其分配任务

部署模式

首先明确下面两个术语:

  • Flink Application:Flink Application 是一个 Java 应用程序,它通过 main 方法(或其他方式)提交一个或多个 Flink Job。提交作业通常通过调用 execute 来完成。
  • Flink Job:是 Flink DAG 图在运行时的具体表现,一个 DAG 对应一个 Flink Job,一般是通过在 Flink Application 中调用 execute 来创建和提交的。

然后再来看 Flink 如何区分不同的部署模式:

  • 集群的生命周期和隔离性保证
  • application 的 main 方法在哪里执行

KpoPTd

Session Mode

多个job共享同一个集群<jobmanager/tastmanager>、job退出集群也不会退出,用户类的main方法再client端运行
适用:需要频繁提交小job,因为每次提交新job时候,不需要去向yarn注册应用

  1. Session模式是预分配资源的,也就是提前根据指定的资源参数初始化一个Flink集群,并常驻在YARN系统中,拥有固定数量的JobManager和TaskManager,该资源中JobManager有且只有一个。
  2. 提交到这个集群的作业可以直接运行,免去了每个提交作业都启动一个完整集群的资源开销,但是Session的资源总量有限,多个作业之间又不是隔离的,故可能会造成资源的争用,如果有一个作业因为异常导致TaskManager宕机,则它上面承载着的所有作业也都会受到影响。
  3. 另外,启动的作业越多,JobManager的负载也就越大。所以,Session模式一般用来部署那些对延迟非常敏感但运行时长较短的作业。

Per-Job Mode (1.15 deprecated).

每个job独享一个集群,job退出集群则退出,用户类的main方法再client端运行
适用:大job,运行时间很长,因为每起一个job,都要去向yarn申请容器启动jm,tm,比较耗时

  1. 为了提供更好的资源隔离保证,在Per-Job模式下,每个提交到YARN上的作业会各自形成单独的Flink集群,拥有专属的JobManager和TaskManager。
  2. 当作业完成时,分配给当前作业的集群将被销毁,所有缓存在集群中的资源(文件等)将被清除。作业之间的资源完全隔离,一个作业的TaskManager失败不会影响其他作业的运行。
  3. Per-Job模式一般用来部署那些长时间运行的作业。

每个job独享一个集群,job退出集群则退出,用户类的main方法再集群上运行

  1. application模式,在该模式下会为每个提交的应用创建一个集群,用户程序的 main 方法将在JobManager集群中而不是客户端运行。
  2. Application模式的会话集群,仅在特定应用程序的作业之间共享,并在应用程序完成时终止。
  3. 在这种体系结构中,Application 模式在不同应用之间提供了资源隔离和负载平衡保证。在特定一个应用程序上,JobManager 执行 main() 可以节省所需的 CPU 周期,还可以节省本地下载依赖项所需的带宽。

部署实践运行与测试

1
2
3
4
5
6
#HADOOP_HOME
export HADOOP_HOME=/opt/module/hadoop-3.1.3
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop
export HADOOP_CLASSPATH=`hadoop classpath`

需要说明的是,Flink on yarn模式部署时,实际上不需要对Flink做任何修改配置,只需要将其解压传输到各个节点之上
如果要实现高可用的方案,这个时候就需要到Flink相应的配置修改参数,具体的配置文件是FLINK_HOME/conf/flink-conf.yaml。

standalone模式还是需要配置 Flink多种模式部署实践中的Standalone 模式
对于Flink on yarn模式,我们并不需要在conf配置下配置 masters和slaves。因为在指定TM的时候可以通过参数“-n”来标识需要启动几个TM;Flink on yarn启动后,如果是在分离式模式你会发现,在所有的节点只会出现一个 YarnSessionClusterEntrypoint进程;如果是客户端模式会出现2个进程一个YarnSessionClusterEntrypoint和一个FlinkYarnSessionCli进程。

Session Mode

Session Mode的运行主要有两个步骤。第一步是启动yarn session,开辟资源,第二步是flink run运行job。
用yarn session启动集群时,有两种方式可以使用,分别是客户端模式和分离模式

客户端模式启动yarn session

使用如下启动命令

1
./bin/yarn-session.sh -n 2 -jm 1024 -tm 1024 -s 2

r3hxgX

  • -n: TaskManager的数量,相当于executor的数量
  • -s: 每个JobManager的core的数量,executor-core,建议将slot的数量设置成每台机器处理器数量
  • -tm: 每个TaskManager的内存大小
  • -jm: JobManager的内存大小
  1. 上面的命令的意思是,同时向Yarn申请3个container,其中2个Container启动TaskManager(-n 2),每个TaskManager 拥有两个Task Slot(-s 2),并且向每个TaskManager的Container申请1024 M 的内存,以及一个ApplicationMaster(Job Manager)。

  2. 在客户端模式下,那作业提交后,资源的大小是由yarn的队列所决定的,多个job提交,资源的占用和竞争都是由yarn所控制。

  3. 客户端模式下,jps查看的进程有两个

    1
    2
    FlinkYarnSessionCli
    YarnSessionClusterEntrypoint

    wolnIQ

  4. hadoop application查看
    JsJP0d

  5. yarn命令行查看

    1
    2
    3
    4
    5
    [root@hadoop103 flink-1.15.2]# yarn application -list
    2022-09-28 17:13:04,195 INFO client.RMProxy: Connecting to ResourceManager at hadoop103/192.168.10.103:8032
    Total number of applications (application-types: [], states: [SUBMITTED, ACCEPTED, RUNNING] and tags: []):1
    Application-Id Application-Name Application-Type User Queue State Final-State Progress Tracking-URL
    application_1664355973790_0001 Flink session cluster Apache Flink root default RUNNING UNDEFINED 100% http://192.168.10.103:39719
  6. 从flink daskboard查看
    aF0TY7

分离模式启动yarn session

使用启动命令如下

1
./bin/yarn-session.sh -d -n 2 -jm 1024 -tm 1024 -s 2 

tE8rCO

  • -d: 指定分离模式
  1. 分离模式在启动的时候会在yarn中常驻一个进程,并且已经确定了之后提交的job的内存等资源的大小,比如8G内存,如果某一个job把8G内存全部占完了,只能是第一个job执行完成把资源释放了,第二个job才能继续执行。

  2. 分离模式下,jps查看进程有一个

    1
    YarnSessionClusterEntrypoint

    KKrpSE

  3. hadoop application查看
    B7txuj

  4. yarn命令行查看

    1
    2
    3
    4
    5
    [root@hadoop102 flink-1.15.2]# yarn application -list
    2022-09-28 17:28:38,451 INFO client.RMProxy: Connecting to ResourceManager at hadoop103/192.168.10.103:8032
    Total number of applications (application-types: [], states: [SUBMITTED, ACCEPTED, RUNNING] and tags: []):1
    Application-Id Application-Name Application-Type User Queue State Final-State Progress Tracking-URL
    application_1664357090524_0002 Flink session cluster Apache Flink root default RUNNING UNDEFINED 100% http://192.168.10.103:41395
  5. 从flink daskboard查看
    23S1K2

提交任务

1
2
./bin/flink run ./examples/batch/WordCount.jar
./bin/flink run ./examples/batch/WordCount.jar --input /opt/tmp/words --output /opt/tmp/test5.txt

Flink 作业提交后,若要在Flink WebUI上查看作业的详细可以通过如下操作

  • 方式1:从终端查看链接
    h6kefu
  • 方式2:从hadoop查看
    点击yarn集群管理器中启动的application_ID,进入如下页面,点击红色方框
    0UT1hK

rphVSR

eAJvh2

yarn session资源释放

1
yarn application -kill application_1664357090524_0002

kEWUT4

启动完整参数

1
./bin/yarn-session.sh 参数
  • -n: 指定TaskManager的数量;
  • -d: 以分离模式运行;
  • -id: 指定yarn的任务ID;
  • -j: Flink jar文件的路径;
  • -jm: JobManager容器的内存(默认值:MB);
  • -nl: 为YARN应用程序指定YARN节点标签;
  • -nm: 在YARN上为应用程序设置自定义名称;
  • -q: 显示可用的YARN资源(内存,内核);
  • -qu: 指定YARN队列;
  • -s: 指定TaskManager中slot的数量;
  • -st: 以流模式启动Flink;
  • -tm: 每个TaskManager容器的内存(默认值:MB);
  • -z: 命名空间,用于为高可用性模式创建Zookeeper子路径;

Per-Job Mode 单作业模式部署

启动

为了方便查看测试过程,我们使用flink包中的SocketWindowWordCount项目,我们需要提前启动socket服务及端口。
1、启动socket服务

1
nc -lk 9002

2、启动Per-Job命令并启动任务

1
./bin/flink run -m yarn-cluster -yjm 1024 -ytm 1024 examples/streaming/SocketWindowWordCount.jar --hostname hadoop102 --port 9002

官方:

1
./bin/flink run -t yarn-per-job  -yjm 1024 -ytm 1024 ./examples/streaming/SocketWindowWordCount.jar --hostname hadoop102 --port 9002
  • -t: 表示启动方式(–target),可取yarn-per-job、yarn-session、run-application等
  • -yjm: JobManager内存
  • -ytm: TaskManager内存大小申请

启动日志
m0T8UX
JPS查看
IBuAys
yarn集群查看
y4cGPP
从上图中我们可以看出,yarn cluster上管理了一个ID为application_1664357090524_0003、name为Flink per-job cluster的资源环境。鼠标点击ID,可计入详细信息界面。
dBLRnB
点击上图中的Tracking URL:ApplicationMaster,可以打开Flink web ui管理界面。
XpuUYQ

测试数据

dX69uU

OtTtLH

当前示例是一个实时流处理,只要socket服务和端口一直处于开启状态,Flink作业就会一直处于运行状态。我们在前面介绍中说过,Per-Job模式的作业,执行完成就会释放yarn资源,停掉socket作业和端口服务,当前yarn所管理的flink 作业和资源就会得到释放。

在socket服务界面 CTR+C 结束服务, Flink作业日志界面会打印如下信息
wQfSaZ

6Jc0Dk

Yarn集群
ATVVuX

Application Mode 应用模式部署

1
2
3
4
5
6
7
8
bin/flink run-application -t yarn-application -p 3\
-Dparallelism.default=3 \
-Djobmanager.memory.process.size=2048m \
-Dtaskmanager.memory.process.size=4096m \
-Dtaskmanager.numberOfTaskSlots=2 \
-Dparallelism.default=10 \
-Dyarn.application.name="application_test" \
./exapmles/batch/WorldCount.jar
  • -t 支持yarn 上(-t yarn-application) 和 k8s 上(-t kubernetes-application)
  • -D 参数指定通用的 运行配置,比如 jobmanager/taskmanager 内存、checkpoint 时间间隔等
  1. jobmanager.memory.process.size=2048m,taskmanager.memory.process.size=4096m是JM和TM内存参数设置
  2. taskmanager.numberOfTaskSlots=2为设置TaskManager slots个数为3
  3. 指定并发还可以使用 -Dparallelism.default=3,-p的作用与这个相同,社区目前倾向使用-D+通用配置代替客户端命令参数-p
  4. yarn.provided.lib.dirs参数:可以预先上传 flink 客户端依赖包到远端共享存储中(一般是hdfs),flink运行时的依赖包省去了上传的过程,避免了网络和存储资源的浪费。同时运行在集群中的所有作业都会使用一份远端 flink 依赖包,并且每个 yarn nodemanager 都会缓存一份,提交速度也会大大提升,对于跨机房提交作业会有很大的优化。
    1
    -Dyarn.provided.lib.dirs="hdfs://flink/libs;hdfs://flink/hotfix" \
    5.应用程序也可以提前上传到hdfs的/flink/examples目录下,将上例中./exapmles/batch/WorldCount.jar修改为hdfs://flink/examples/WorldCount.jar,也可以将yarn.provided.lib.dirs 配置到 conf/flink-conf.yaml(如果没有增加一个)可参考官网配置说明,这时提交作业就和上面示例的普通作业没有区别了

常用命令

查看Flink Yarn Application

1
yarn application -list

停止Flink Yarn Application

1
yarn application -kill appID

相关链接

Flink 1.14.2 on Yarn 遇到的坑
flink1.10三节点集群之yarn-session模式

一、Local 模式

Local 模式是 Flink 提供的最简单部署模式,一般用来本地测试和演示使用。
进入 Flink 官网,下载 1.15.2 版本安装包 flink-1.15.2-bin-scala_2.12.tgz,注意此处选用对应 scala 版本为 scala 2.12 的安装包。
https://flink.apache.org/zh/downloads.html

将压缩包下载到本地,并且直接进行解压,使用 Flink 默认的端口配置,直接运行脚本启动:

1
tar -zxvf flink-1.15.2-bin-scala_2.12.tgz
1
./bin/start-cluster.sh

我们直接访问本地的 8081 端口,可以看到 Flink 的后台管理界面,验证 Flink 是否成功启动。

我们尝试提交一个测试任务:

1
./bin/flink run examples/batch/WordCount.jar

二、Standalone 模式

1.1 环境配置

Flink 安装部署的学习时,需要准备 3 台 Linux 机器。具体要求如下:

  • 系统环境为 CentOS 7.9 版本。
  • 安装 Java 8。
    1
    2
    3
    4
    [root@hadoop102 flink-1.15.2]# java -version
    java version "1.8.0_333"
    Java(TM) SE Runtime Environment (build 1.8.0_333-b02)
    Java HotSpot(TM) 64-Bit Server VM (build 25.333-b02, mixed mode)
  • 配置集群节点服务器间时间同步以及免密登录,关闭防火墙。

1.2 集群节点

Me0cWj

节点服务器 hadoop102 hadoop103 hadoop104
ip 192.168.10.102 192.168.10.103 192.168.10.104
角色 JobManager TaskManager TaskManager

1.3 下载并配置运行

进入 Flink 官网,下载 1.15.2 版本安装包 flink-1.15.2-bin-scala_2.12.tgz,注意此处选用对应 scala 版本为 scala 2.12 的安装包。
https://flink.apache.org/zh/downloads.html

  • 1、解压到/opt/module目录下

    1
    [root@hadoop102 flink]$ tar -zxvf flink-1.15.2-bin-scala_2.12.tgz -C /opt/module/
  • 2、进入/opt/module下 的Flink 目录下

    1
    [root@hadoop102 flink-1.15.2]$ cd /opt/module/flink-1.15.2
  • 3、进入conf目录中

    1
    [root@hadoop102 flink-1.15.2]$ cd /conf
  • 4、配置flink-conf.yaml文件

    1
    [root@hadoop102 conf]$ vim flink-conf.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
    # jobManager 的IP地址
    jobmanager.rpc.address: hadoop102
    # jobManager JVM heap内存大小
    jobmanager.heap.size: 1024m
    # jobManager 的rpc通信端口
    jobmanager.rpc.port: 6123
    # jobManager 绑定到的网络接口的地址
    jobmanager.bind-host: hadoop102
    # jobmanager进程使用的所有内存大小
    jobmanager.memory.process.size: 1600m
    # taskmanager服务地址 -> 不同主机改主机名称或ip 如:hadoop103、hadoop104
    taskmanager.bind-host: hadoop102/hadoop103/hadoop104
    # taskManager绑定到的网络接口的地址 -> 不同主机改主机名称或ip 如:hadoop103、hadoop104
    taskmanager.host: hadoop102/hadoop103/hadoop104
    # taskmanager进程使用的所有内存大小
    taskmanager.memory.process.size: 1728m
    # 每个TaskManager 提供的任务 slots 数量大小
    # 它的意思是当前task能够同时执行的线程数量 (实际生产环境建议是CPU核心-1,这里笔者写2)
    taskmanager.numberOfTaskSlots: 2
    # 默认并行度
    parallelism.default: 1
    # 重启策略
    jobmanager.execution.failover-strategy: region
    # 客户端应该用来连接到服务器的地址
    rest.address: hadoop102
    # web服务绑定的address
    rest.bind-address: hadoop102
  • 5、配置masters文件(根据实际环境设置)

    1
    hadoop102:8081
  • 6、配置workers文件(根据实际环境设置)

    1
    2
    hadoop103
    hadoop104
  • 7、分发其他机器

    1
    [root@hadoop102 conf]$ xsync /opt/module/flink-1.15.2
  • 8、启动集群

    1
    [root@hadoop102 flink-1.15.2]$ bin/start-cluster.sh 
  • 9、Web UI 默认端口 http://hadoop102:8081
    plfyWk

  • 10、关闭集群

    1
    [root@hadoop102 flink-1.15.2]$ bin/stop-cluster.sh 

1.4 问题

  • MacBook M1虚拟机
  • Centos7操作系统

Flink TaskManager启动报错,报错信息如下:

1
2
Error: VM option ‘UseG1GC’ is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.
Error: Could not create the Java Virtual Machine.

处理办法

  • 找到bin/taskmanager.sh文件
  • 搜索UseG1GC
  • 直接删除 -XX:+UseG1GC

三、on yarn 模式

on yarn中又分

  • Session Mode
  • Per-Job Mode (1.15 deprecated).
  • Application Mode(Flink 1.11引入)
    篇幅较大,单独:
    Flink on yarn

需求

  • 局域网A(家庭):群晖一台 192.168.199.186 、 路由器192.168.199.1 小米路由器;
  • 局域网B:我的macbook pro 10.8.99.45;

beoXra

tip: 图中100.x.y.z为macbook pro(junyao)群晖(homeDS)公司电脑(VM-0-2-centos)组网后的ip,图中192.168.0.x,为本例局域网A192.168.199.x内部设备局域网ip

假设家庭内网有一台群晖(homeDS)安装了 Tailscale 客户端,我们希望其他 Tailscale 客户端macbook pro(junyao)公司电脑(VM-0-2-centos)可以直接通过家中的局域网 IP段(例如 192.168.199.0/24)访问家庭内网的任何一台设备,

我们来尝试实现组网后macbook pro 10.8.99.45访问我的小米路由器192.168.199.1

搭建headscale服务端

我们在headscale私有部署 中已经将 macbook pro(junyao)群晖(homeDS)公司电脑(VM-0-2-centos)进行了组网,组网结果如下,
我们可以通过组网后的ip100.64.0.1100.64.0.4100.64.0.3 进行p2p互相访问

1
2
3
4
ID | Hostname      | Name                   | NodeKey | Namespace | IP addresses                  | Ephemeral | Last seen           | Online | Expired
1 | junyao | junyao-lxgc8rs7 | [kEPjO] | junyao | 100.64.0.1, fd7a:115c:a1e0::1 | false | 2022-09-15 02:39:21 | online | no
2 | homeDS | homeds-2xqvj919 | [96Nns] | junyao | 100.64.0.4, fd7a:115c:a1e0::4 | false | 2022-09-15 02:39:00 | online | no
3 | VM-0-2-centos | vm-0-2-centos-ieo6arqr | [1M6Gs] | junyao | 100.64.0.3, fd7a:115c:a1e0::3 | false | 2022-09-15 02:39:56 | online | no

此时我们要实现的就是 macbook pro(junyao) 访问 群晖(homeDS)内部局域网的设备访问,比如小米路由器。

配置群晖tailscale

  • 1、我们在群晖安装tailscale客户端
    DaY8pW
  • 2、进入ssh,在群晖系统 /etc/sysctl.conf 中增加
    1
    2
    net.ipv4.ip_forward=1
    net.ipv6.conf.all.forwarding=1
    让其支持 IPv4 与 IPv6 路由转发
  • 3、开启tailscale并设置--advertise-routes 让其打通局域网内部的路由访问,这个网段的所有设备都可以被访问
    1
    tailscale up --accept-dns=false --advertise-routes=192.168.199.0/24 --login-server=http://<ip>:8080
  • 4、headscale服务端设置
    在 headscale 端查看路由,可以看到相关路由是关闭的。
    1
    2
    3
    4
    5
    headscale nodes list|grep homeDS
    2 | homeDS | homeds-2xqvj919 | [96Nns] | junyao | 100.64.0.4, fd7a:115c:a1e0::4 | false | 2022-09-15 02:49:59 | online | no
    headscale routes list -i 2
    Route | Enabled
    192.168.199.0/24 | false
    开启群晖路由:
    1
    2
    3
    4
    headscale routes enable -i 2 -r "192.168.199.0/24"

    Route | Enabled
    192.168.199.0/24 | true

官方文档:Subnet routers and traffic relay nodes

现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器了
我们试试用macbook pro 10.8.99.45访问我的小米路由器192.168.199.1

2ELMDS

背景

上次我们完成了headscale私有部署,但是为了保证headscale组网中的稳定和可靠性,为设备p2p连接设置一个保底措施,我们需要自建derp服务器。

什么是DERP

DERP 即 Detoured Encrypted Routing Protocol,这是 Tailscale 自研的一个协议:

  • 它是一个通用目的包中继协议,运行在 HTTP 之上,而大部分网络都是允许 HTTP 通信的。
  • 它根据目的公钥(destination’s public key)来中继加密的流量(encrypted payloads)。

Tailscale 会自动选择离目标节点最近的 DERP server 来中继流量
Tailscale 使用的算法很有趣: __所有客户端之间的连接都是先选择 DERP 模式(中继模式),这意味着连接立即就能建立(优先级最低但 100% 能成功的模式),用户不用任何等待。然后开始并行地进行路径发现,通常几秒钟之后,我们就能发现一条更优路径,然后将现有连接透明升级(upgrade)过去,变成点对点连接(直连)__。

因此, DERP 既是 Tailscale 在 NAT 穿透失败时的保底通信方式(此时的角色与 TURN 类似),也是在其他一些场景下帮助我们完成 NAT 穿透的旁路信道。换句话说,它既是我们的保底方式,也是有更好的穿透链路时,帮助我们进行连接升级(upgrade to a peer-to-peer connection)的基础设施。

Tailscale 官方内置了很多 DERP 服务器,分步在全球各地,惟独不包含中国大陆,原因你懂得。这就导致了一旦流量通过 DERP 服务器进行中继,延时就会非常高。而且官方提供的 DERP 服务器是万人骑,存在安全隐患。

官方内置了很多 DERP 服务器

1
2
3
4
5
6
7
8
9
10
11
12
- tok: 96.6ms  (Tokyo)
- sfo: 179.8ms (San Francisco)
- sea: 181.3ms (Seattle)
- dfw: 218.6ms (Dallas)
- ord: 229.7ms (Chicago)
- nyc: 233.8ms (New York City)
- fra: 257.7ms (Frankfurt)
- lhr: 259.1ms (London)
- sin: 265ms (Singapore)
- syd: 317.1ms (Sydney)
- sao: 352.8ms (São Paulo)
- blr: 368.5ms (Bangalore)

为了实现低延迟、高安全性,我们可以参考 Tailscale 官方文档自建私有的 DERP 服务器。
Custom DERP Servers

部署DERP

还是docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3.5'
services:
derper:
container_name: derper
image: fredliang/derper
restart: always
volumes:
- ./cert:/cert
ports:
- 3478:3478/udp
- 23479:23479
environment:
DERP_DOMAIN: derp.XXXX.cn
DERP_ADDR: ":23479"
DERP_CERT_MODE: manual
DERP_CERT_DIR: /cert
  • stunport: 3478 默认情况下也会开启 STUN 服务,UDP 端口是 3478
  • derpport: 23479
1
2
3
2022/09/06 01:21:27 no config path specified; using /var/lib/derper/derper.key
2022/09/06 01:21:27 derper: serving on :23479 with TLS
2022/09/06 01:21:27 running STUN server on [::]:3478

部署好 derper 之后,就可以修改 Headscale 的配置来使用自定义的 DERP 服务器了。Headscale 可以通过两种形式的配置来使用自定义 DERP:

  • 一种是在线 URL,格式是 JSON,与 Tailscale 官方控制服务器使用的格式和语法相同。

  • 另一种是本地文件,格式是 YAML。
    我们可以直接使用本地的 YAML 配置文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # /etc/headscale/derp.yaml
    regions:
    901:
    regionid: 901
    regioncode: gz
    regionname: Tencent Guangzhou
    nodes:
    - name: 901a
    regionid: 901
    hostname: '实际域名'
    ipv4: '可不需要'
    stunport: 3478
    stunonly: false
    derpport: 23479
  • regions 是 YAML 中的对象,下面的每一个对象表示一个可用区,每个可用区里面可设置多个 DERP 节点,即 nodes。
    每个可用区的 regionid 不能重复。

  • 每个 node 的 name 不能重复。

  • regionname 一般用来描述可用区,regioncode 一般设置成可用区的缩写。

  • ipv4 字段不是必须的,如果你的域名可以通过公网解析到你的 DERP 服务器地址,这里可以不填。如果你使用了一个二级域名,而这个域名你并没有在公共 DNS server 中添加相关的解析记录,那么这里就需要指定 IP(前提是你的证书包含了这个二级域名,这个很好支持,搞个泛域名证书就行了)。

  • stunonly: false 表示除了使用 STUN 服务,还可以使用 DERP 服务。

  • hostname和 ipv4部分根据你的实际情况填写。

接下来还需要修改 Headscale 的配置文件,引用上面的自定义 DERP 配置文件。需要修改的配置项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# /etc/headscale/config.yaml
derp:
# List of externally available DERP maps encoded in JSON
urls:
- https://controlplane.tailscale.com/derpmap/default

# Locally available DERP map files encoded in YAML
#
# This option is mostly interesting for people hosting
# their own DERP servers:
# https://tailscale.com/kb/1118/custom-derp-servers/
#
# paths:
# - /etc/headscale/derp-example.yaml
paths:
- /etc/headscale/derp.yaml

# If enabled, a worker will be set up to periodically
# refresh the given sources and update the derpmap
# will be set up.
auto_update_enabled: true

# How often should we check for DERP updates?
update_frequency: 24h

在 Tailscale 客户端上使用以下命令查看目前可以使用的 DERP 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ tailscale netcheck
Report:
* UDP: true
* IPv4: yes, 14.154.29.252:50713
* IPv6: yes, [::ffff:14.154.29.252]:50713
* MappingVariesByDestIP: false
* HairPinning: false
* PortMapping: UPnP, NAT-PMP, PCP
* Nearest DERP: Tencent Guangzhou
* DERP latency:
- gz: 8.5ms (Tencent Guangzhou) -----> 这就是我们刚建的derp服务器
- tok: 96.6ms (Tokyo)
- sfo: 179.8ms (San Francisco)
- sea: 181.3ms (Seattle)
- dfw: 218.6ms (Dallas)
- ord: 229.7ms (Chicago)
- nyc: 233.8ms (New York City)
- fra: 257.7ms (Frankfurt)
- lhr: 259.1ms (London)
- sin: 265ms (Singapore)
- syd: 317.1ms (Sydney)
- sao: 352.8ms (São Paulo)
- blr: 368.5ms (Bangalore)

tailscale netcheck 实际上只检测 3478/udp 的端口, 就算 netcheck 显示能连,也不一定代表 23479 端口可以转发流量。最简单的办法是直接打开 DERP 服务器的 URL:https://derp.XXXX.cn:23479,如果看到如下页面,且地址栏的 SSL 证书标签显示正常可用,那才是真没问题了。
NUGpWd

Tailscale 命令行工具来测试:

查看与通信对端的连接方式及状态

1
2
3
4
tailscale status

100.64.0.1 junyao-lxgc8rs7 junyao macOS -
100.64.0.2 homeds-2xqvj919 junyao linux active; direct [::ffff:14.154.29.252]:41641, tx 518960 rx 7674032

ping一下

1
2
3
4
tailscale ping 100.64.0.2

pong from homeds-2xqvj919 (100.64.0.2) via [::ffff:14.154.29.252]:41641 in 6ms --->通过点对点
pong from homeds-2xqvj919 (100.64.0.2) via DERP(thk) in 104ms --->通过DERP

这个tailscale ping更加友好一点,会直接告诉你是通过DERP中继服务器还是点对点来和对方通信的。

DERP安全

我们总要设置一下DERP的访问权限,不能成为万人骑

步骤:
1、在 DERP 服务器上安装 Tailscale。
2、derper 启动时加上参数 –verify-clients

tip:因为derp是通过本地tailscale进行检测,所以docker部署的derper必须在容器内启有Tailscale客户端的实例,我们把服务器的/var/run/tailscale/tailscaled.sock映射进入derp容器目录中,让derp能检测到tailscale
tip:如果是使用docker安装的tailscale,请将/var/run/tailscale/tailscaled.sock映射出来,然后再映射进入derp,即让derp和tailscale容器共享volume(/var/run/tailscale/tailscaled.sock)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3.5'
services:
derper:
container_name: derper
image: fredliang/derper
restart: always
volumes:
- ./cert:/cert
- /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
ports:
- 3478:3478/udp
- 23479:23479
environment:
DERP_DOMAIN: derp.webhunt.cn
DERP_ADDR: ":23479"
DERP_CERT_MODE: manual
DERP_CERT_DIR: /cert
DERP_VERIFY_CLIENTS: "true"

需求

  • 办公需求:
    设备:
    1、synology DS918+
    2、macbook pro m1
    3、mac mini
    问题:访问家里nas慢的问题,及工作效率能快速访问的问题

  • 公司跨机房需求:
    1、erp金蝶系统-sqlServer服务-公司内部机房
    2、采购系统-mongodb服务-阿里云服务器
    问题:采购系统上访问ERP速度慢的问题,及双流cdc的需求

设想这样一个问题:在北京和上海各有一台局域网的机器(例如一台是家里的台式机,一 台是连接到星巴克 WiFi 的笔记本),二者都是私网 IP 地址,但可以访问公网, 如何让这两台机器通信呢

既然二者都能访问公网,那最简单的方式当然是在公网上架设一个中继服务器: 两台机器分别连接到中继服务,后者完成双向转发。这种方式显然有很大的性能开销,而 且中继服务器很容易成为瓶颈。

有没有办法不用中继,让两台机器直接通信呢

那就是:跨过公网(互联网)实现端到端直连。

TailScale 是什么, Headscale又是什么

TailScale

TailScale 你可以理解为 VPN,或者说 Wireguard 外面包了一层壳子。它可以将一些设备连接起来,形成一个虚拟局域网。一个很简单的例子,你可以在星巴克里,读取家里任意电脑上的文件。

比如最常见的需求就是,公司有一个内网办公环境,当你外出办公时,也希望你的电脑能够接入办公网络。 因为外网的机器和内网的机器不能互联,所以一般会有一个中心服务器, 所有的子节点都和中心服务器相连,然后中心服务器转发所有的流量。
5Z0qxl

这样做的缺点显而易见,首先是中心服务器(hub)会成为瓶颈。 其次,某种极端情况下,如果节点 A 和 节点 B 距离非常近,但是都离 hub 很远, 这样就会导致非常高的延迟。
Rje9yw

那么我们就会想,能不能让节点间直接互联呢? 这就是 mesh VPN,其实现就是 wireguard。
pQ8X8e
wireguard 的每一个节点都会存储其他所有节点的信息,并且和其他所有的节点都建立 tls 连接。 如果涉及到内网穿透的话,那么你需要找到一台处于网关位置的节点(内外网都可达),将其设置为 coordinator, 扮演类似于 hub 的角色, 分发穿透内外网的流量。

wireguard 的缺点在于:

  • 配置比较繁琐
  • 维护也比较困难,增删节点都需要改动所有节点的配置

基于上述这些痛点,TailScale 做了一些改进:

1、在原有的 ICE、STUN 等 UDP 协议外,实现了 DERP TCP 协议来实现 NAT 穿透
2、基于公网的 coordinator 服务器下发 ACL 和配置,实现节点动态更新
3、通过第三方(如 Google) SSO 服务生成用户和私钥,实现身份认证

对我来说 Tailscale 相对于 Wireguard 最大的优势有 3 点:
1、配置简单
2、支持 P2P 和中继自动切换
3、支持自建中继节点

作为一种搭建虚拟局域网的工具,相比于传统VPN而言,所有节点之间都可以进行P2P连接,也就是全互联模式,效率更高。

为了基于wireguard实现更完美的VPN工具,现在已经出现了很多项目,如Netmaker,通过可视化界面来配置全互联模式,并支持UDP打洞、多租户等高端功能,几乎适配所有平台。然而现实世界是复杂的,无法保证所有的NAT都能打洞成功,而且Netmaker目前不支持fallback机制,如打洞失败,无法fallback中继节点。而Tailscale支持fallback,可以尽最大努力实现全互联模式,部分节点即使打洞不成功,也可通过中继节点在虚拟局域网内畅通无阻。

简而言之,我们可以认为 TailScale 是更为易用版的、功能封装更为完善的 wireguard。

Headscale

Headscale看名字就知道是和 Tailscale 对着干的,Tailscale 的客户端是不收费的,服务端是不开源的,超过 20 个设备就需要付费了,并且Tailscale的服务不在国内,我们不可能把服务端安全的交给Tailscale,私有自建才是出入。Headscale 是个第三方开源版本的 Tailscale 的服务端,除了「网站界面」之外该有的功能都有,因此我们使用Headscale自建私有服务。

解决方案

所以本次的方案:
1、阿里云服务器 部署 Headscale 服务端
2、自己的 Mac 和 Nas 上,使用 Tailscale 作为客户端

或公司,mongodb所在的服务器与sqlServer所在的服务器组网

服务端

Docker 部署

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.5'
services:
headscale:
image: headscale/headscale:latest-alpine
container_name: headscale
volumes:
- ./config:/etc/headscale
- ./data/data:/var/lib/headscale
ports:
- 8080:8080
command: headscale serve
restart: unless-stopped

手动新建一下配置文件,放入./config

1
wget https://github.com/juanfont/headscale/raw/main/config-example.yaml -O config.yaml

编辑一下它:

  • server_url 这个东西是一个简单的「HTTP 设备认证页面」,后面需要暴露在公网上,其他设备如果想加入到你的网络里面,需要访问这个地址,拿到一个 Token。有域名的话推荐用域名+nginx/caddy 反向代理没域名的话用 ip+端口。
  • ip_prefixes 可以根据自己喜好来改,留默认的当然也行

docker-compose up -d启动 docker
下一步,创建一个「租户」,你可以理解为一个用户。进到 headscale 的 docker 里面,执行

1
headscale namespaces create SOME_NAME

SOME_NAME随意替换

客户端

并非全部客户端都支持(或全部支持)将 Headscale 作为后端,目前适配情况如下:

操作系统 支持情况 解决方案
Linux 原生支持
Windows 修改注册表
macOS 需要添加描述文件
Android
iOS 目前不支持

Mac 端:AppStore 版客户端

由于是使用的「非官方版控制器」,所以我们需要稍微 hack 一下,将软件里面默认的「tailscale 官方控制服务器地址」改为我们自己搭建的 Headscale 地址。

访问 http://server_url/apple,下载并安装一个描述文件。

然后打开 Tailscale,点击登录,会看到一个英文界面,里面有一行命令

1
headscale -n NAMESPACE nodes register --key SOME_HEX_VALUE

将里面的 NAMESPACE 换成你创建的租户名称,然后去服务端 docker 里面,执行它。你就会发现,你的 mac 已经登录成功了。

Linux端:Docker 版客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.3'
services:
tailscaled:
container_name: tailscaled
image: tailscale/tailscale
network_mode: host
privileged: true
restart: always
cap_add:
- net_admin
- sys_module
volumes:
- ./lib:/var/lib
- /dev/net/tun:/dev/net/tun
command: sh -c "mkdir -p /var/run/tailscale && ln -s /tmp/tailscaled.sock /var/run/tailscale/tailscaled.sock && tailscaled"

启动容器后,需要进入容器,输入这个东西进行登录:

1
2
tailscale up --login-server=http://server_url --accept-routes=true --accept-dns=false

如果没问题,那么会提示你访问一个网址,拿到 Token,回到 Server 端进行登录就好。

synology

Access Synology NAS from anywhere
Tailscale Packages

tailscale up 参数

  • –hostname 设置名称
  • –accept-routes 接受服务端配置的路由
  • –accept-dns 推荐将 DNS 功能关闭,因为它会覆盖系统的默认 DNS
  • –advertise-routes 申请路由到该节点,Tailscale 实现「出口节点」,即打通局域网内部的路由访问,这个网段的所有设备都可以被访问
  • –advertise-exit-node 可以建立数据导向节点exit node,即本机访问互联网皆通过此节点 bolean
  • –exit-node 指定出口节点,导向所有流量经这出口节点

headscale命令

通过服务端授权客户端

1
headscale -n default nodes register --key 905cf165204800247fbd33989dbc22be95c987286c45aac3033937041150d846

查看注册的节点

1
2
3
4
headscale nodes list

ID | Name | NodeKey | Namespace | IP addresses | Ephemeral | Last seen | Online | Expired
1 | coredns | [Ew3RB] | default | 10.1.0.1 | false | 2022-03-20 09:08:58 | online | no

通过 Pre-Authkeys 接入

前面的接入方法都需要服务端同意,步骤比较烦琐,其实还有更简单的方法,可以直接接入,不需要服务端同意。

首先在服务端生成 pre-authkey 的 token,有效期可以设置为 24 小时:

1
headscale preauthkeys create -e 24h -n default

查看已经生成的 key:

1
headscale -n default preauthkeys list

现在新节点就可以无需服务端同意直接接入了:

1
tailscale up --login-server=http://<HEADSCALE_PUB_IP>:8080 --accept-routes=true --accept-dns=false --authkey $KEY

打通局域网

WZtD67
假设你的家庭内网有一台 Linux 主机(比如 Nas)安装了 Tailscale 客户端,我们希望其他 Tailscale 客户端可以直接通过家中的局域网 IP(例如 192.168.100.0/24) 访问家庭内网的任何一台设备。

官方文档:Subnet routers and traffic relay nodes
群辉具体操作见:headscale组网打通群辉局域网内部访问

linux 配置方法很简单,首先需要设置 IPv4 与 IPv6 路由转发:

1
2
3
echo 'net.ipv4.ip_forward = 1' | tee /etc/sysctl.d/ipforwarding.conf
echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/ipforwarding.conf
sysctl -p /etc/sysctl.d/ipforwarding.conf

客户端修改注册节点的命令,在原来命令的基础上加上参数 --advertise-routes=192.168.100.0/24

1
tailscale up --login-server=http://<HEADSCALE_PUB_IP>:8080 --accept-routes=true --accept-dns=false --advertise-routes=192.168.100.0/24

在 Headscale 端查看路由,可以看到相关路由是关闭的。

1
2
3
4
5
6
7
headscale nodes list|grep nas
6 | nas | [7LdVc] | default | 10.1.0.6 | false | 2022-03-20 15:50:46 | online | no

headscale routes list -i 6
Route | Enabled

192.168.100.0/24 | false

开启路由:

1
2
3
headscale routes enable -i 6 -r "192.168.100.0/24"
Route | Enabled
192.168.100.0/24 | true

其他节点查看路由结果:

1
2
ip route
192.168.100.0/24 dev tailscale0

现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器了

为了能让headscale稳定,我们可以自建中继服务器
headscale之DERP中继服务器自建

相关链接

NAT 穿透是如何工作的:技术原理及企业级实践

0%