0%

什么是 API 网关?

所谓网关,主要作用就是连接两个不同网络的设备,而今天所讲的 API 网关是指承接和分发客户端所有请求的网关层。

为什么需要网关层?最初是单体服务时,客户端发起的所有请求都可以直接请求到该服务,但随着产品用户越来越多,单体应用存在显而易见的单点问题,除此之外,当单体应用大小升至几个 G 时,持续发布将会非常缓慢,所以服务的拆分成为了必然趋势。

当服务拆分为多个之后,我们不得不面临一个问题,就是如何控制用户请求到对应服务节点,于是网关层应运而生,它不仅可以负责负载均衡,还可以让它处理认证校验、请求限流、日志记录以及监控服务节点等等。

当然,网关层并不需要我们手动实现,市面上有很多 API 网关开源项目,比如 Zuul、Kong、Tyk 等,今天主要介绍 Kong。

安装 Kong

Kong 是一个在 Nginx 中运行的 Lua 程序,由 lua-nginx-module 实现,和 Openresty 一起打包发行,支持多种操作环境下的安装,可以用来做 HTTP 基本认证、密钥认证、TCP、UDP、文件日志、API 请求限流、请求转发等等。

第一步,创建一个 docker 网络。

1
$ docker network create kong-net

创建用于存储 Kong 数据的数据库,可以使用 Cassandra 或 PostgreSQL,本示例采用 Cassandra。

Cassandra 是由 Facebook 开发的分布式 NoSQL 数据库。

1
2
3
4
$ docker run -d --name kong-database \
--network=kong-net \
-p 9042:9042 \
cassandra:3

初始化数据到 Cassandra 数据库。

1
2
3
4
5
6
7
$ docker run --rm \
--network=kong-net \
-e "KONG_DATABASE=cassandra" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
kong:latest kong migrations bootstrap

启动 Kong 容器连接数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker run -d --name kong \
--network=kong-net \
-e "KONG_DATABASE=cassandra" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-p 8000:8000 \
-p 8443:8443 \
-p 0.0.0.0:8001:8001 \
-p 0.0.0.0:8444:8444 \
kong:latest

通过 curl 模拟请求本地 8001 端口可以获取 Kong 的详细信息。截止目前整个 kong 服务就跑起来了,接下来可以注册服务节点到 kong 中。

1
$ curl -i http://localhost:8001/

注册服务

注册名为 baidu-service 的服务到 kong,如果请求匹配到该服务会跳转至 url。

1
2
3
4
$ curl -i -X POST \
--url http://localhost:8001/services/ \
--data 'name=baidu-service' \
--data 'url=http://baidu.com'

注册成功之后会得到如下提示。

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 201 Created
Date: Sat, 16 May 2020 06:35:56 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/2.0.4
Content-Length: 292
X-Kong-Admin-Latency: 103

{"host":"baidu.com","created_at":1589610956,"connect_timeout":60000,"id":"6660aaa7-5afa-4f02-85f8-11dfb81fba84","protocol":"http","name":"baidu-service","read_timeout":60000,"port":80,"path":null,"updated_at":1589610956,"retries":5,"write_timeout":60000,"tags":null,"client_certificate":null}

服务配置路由

服务添加成功后,需要告知 kong 什么样的请求才使用该服务,而这个规则被称为 route,也就是路由,路由的作用是按规则匹配客户端的请求,然后转发到对应的 service,每个 route 都对应一个 service,一个 service 可能有多个 route。

1
2
3
$ curl -i -X POST \
--url http://localhost:8001/services/baidu-service/routes \
--data 'hosts[]=baidu.com'

以上代码的作用是当请求域名是 baidu.com 时,则将请求转发到 baidu-service 指定的 url,我们可以通过 curl 模拟测试。

1
2
3
$ curl -i -X GET \
--url http://localhost:8000/ \
--header 'Host: baidu.com'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 81
Connection: keep-alive
Date: Sat, 16 May 2020 06:45:12 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Cache-Control: max-age=86400
Expires: Sun, 17 May 2020 06:45:12 GMT
X-Kong-Upstream-Latency: 92
X-Kong-Proxy-Latency: 17
Via: kong/2.0.4

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

负载均衡配置

与 nginx 同理,先创建一个 upstream,名为 hello。

1
$ curl -X POST http://localhost:8001/upstreams --data "name=hello"
1
{"created_at":1589633009,"hash_on":"none","id":"3bab80bb-7e62-40c8-8b7c-7efdcc329675","algorithm":"round-robin","name":"hello","tags":null,"hash_fallback_header":null,"hash_fallback":"none","hash_on_cookie":null,"host_header":null,"hash_on_cookie_path":"\/","healthchecks":{"threshold":0,"active":{"https_verify_certificate":true,"type":"http","http_path":"\/","timeout":1,"unhealthy":{"http_statuses":[429,404,500,501,502,503,504,505],"tcp_failures":0,"timeouts":0,"http_failures":0,"interval":0},"healthy":{"http_statuses":[200,302],"interval":0,"successes":0},"https_sni":null,"concurrency":10},"passive":{"unhealthy":{"http_failures":0,"http_statuses":[429,500,503],"tcp_failures":0,"timeouts":0},"healthy":{"http_statuses":[200,201,202,203,204,205,206,207,208,226,300,301,302,303,304,305,306,307,308],"successes":0},"type":"http"}},"hash_on_header":null,"slots":10000}

为 upstream 添加两个负载均衡的节点,我就拿京东和淘宝来做测试了。

1
2
curl -X POST http://localhost:8001/upstreams/hello/targets --data "target=jd.com" --data "weight=100"
curl -X POST http://localhost:8001/upstreams/hello/targets --data "target=taobao.com" --data "weight=50"

如上配置就相当于 nginx 中的

1
2
3
4
upstream hello {
server jd.com weight=100;
server taobao.com weight=50;
}

接下来创建一个 service 指向 upstream,host 即对应 upstream 名。

1
curl -X POST http://localhost:8001/services --data "name=hello" --data "host=hello"

为 service 创建路由,凡是包含 /hello 的地址全部走 hello upstream。

1
2
curl -i -X POST --url http://localhost:8001/services/hello/routes --data 'paths[]=/hello'
curl -X POST --url http://localhost:8001/routes --data 'paths[]=/hello' --data 'service.id=8ad06aa5-be0a-4763-a84d-90b8046765f5'

现在访问 localhost:8000 就可以看到已经成功做了负载均衡。

限流

因为服务器不能处理无限量的请求,为了避免请求数高到服务器无法处理,必须做限流操作。关于限流,kong 使用了 rate-limiting 插件,它可以在 service、route、consumer 不同粒度上控制请求,配置非常简单。

1
2
3
4
curl -X POST http://kong:8001/services/{service}/plugins \
--data "name=rate-limiting"
--data "config.second=5" \
--data "config.hour=10000"

config.second 代表一秒钟内处理的最大请求次数,config.hour 表示一小时内最大请求次数。

Route 的示例

1
2
3
4
curl -X POST http://kong:8001/routes/{route}/plugins \
--data "name=rate-limiting"
--data "config.second=5" \
--data "config.hour=10000"

Consumer 的示例

1
2
3
4
curl -X POST http://kong:8001/consumers/{consumer}/plugins \
--data "name=rate-limiting-advanced" \
--data "config.second=5" \
--data "config.hour=10000"

背景

作为一名程序员,家里多多少少会有一些落了灰的电脑,如果把闲置的电脑变成服务器,不仅有良好的配置,还能用来做各种测试,那就再好不过了。但是局域网的设备怎么被外网访问呢?这就靠内网穿透来实现了。

内网穿透又叫 NAT 穿透,常用的工具有很多,比如 ngrok、花生壳、frp等,因为我使用的是 frp,这也是本篇文章的主题。

NAT 是在 IP 数据包通过路由器或防火墙的时候重写 IP 地址的技术。因为现在的公网 IP 数量有限,国家不能给每个设备分配一个公网 IP,所以只能多台计算机共用一个公网 IP 对外通讯,这样就需要进行网络转换,而 NAT 的目的正是如此。

基本实现原理

frp 分为服务端与客户端,前者运行在有公网 IP 的服务器上,后者运行在局域网内的设备上,服务端默认会先开放 7000 端口,然后客户端与其相连。

同时客户端可以开启用于 ssh 的端口,与服务端的某个端口做映射,这样我们在终端访问服务端的端口时,会自动转发到客户端去。

除了 ssh 端口之外,frp 还支持 web 端口来接收 http 访问。

安装使用

目前需要公网服务器、内网服务器各一台,我的内网服务器重装了 linux 系统,方便试验各类工具。

服务端安装配置

1
2
3
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
tar zxvf frp_0.33.0_linux_amd64.tar.gz
cd frp_0.33.0_linux_amd64/

服务端的配置文件是 frps.ini,默认绑定 7000 端口,如果购置了云服务器,注意打开 7000 端口。

1
2
[common]
bind_port = 7000

通过 fprs 二进制文件启动 frp 服务。

1
./frps -c ./frps.ini

如下提示即是安装成功。

1
2
3
4
5
2020/05/15 22:16:29 [I] [service.go:178] frps tcp listen on 0.0.0.0:7000
2020/05/15 22:16:29 [I] [root.go:209] start frps success
2020/05/15 22:16:38 [I] [service.go:432] [e3c5096bd4291972] client login info: ip [14.114.230.168:44422] version [0.24.1] hostname [] os [linux] arch [amd64]
2020/05/15 22:16:38 [I] [tcp.go:63] [e3c5096bd4291972] [ssh] tcp proxy listen port [7001]
2020/05/15 22:16:38 [I] [control.go:445] [e3c5096bd4291972] new proxy [ssh] success

客户端安装配置

把自己的破电脑拿出来,以同样的方式下载 frp。

1
2
3
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
tar zxvf frp_0.33.0_linux_amd64.tar.gz
cd frp_0.33.0_linux_amd64/

客户端的配置文件是 frpc.ini。

1
2
3
4
5
6
7
8
9
[common]
server_addr = 127.0.0.1
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

common 为通用配置

  • server_addr 为公网服务器 IP 地址
  • server_port 为公网服务器配置的 7000 端口

ssh 用于终端命令行访问

  • type 连接类型,默认为 tcp
  • local_ip 本地 IP
  • local_port 用于 ssh 的端口号,默认 22
  • remote_port 映射的服务端端口,访问该端口时默认转发到客户端的 22 端口

启动客户端进程

1
./frpc -c ./frpc.ini

如有以下提示则代表与服务端连接成功

1
2
3
2020/05/15 22:34:49 [I] [service.go:282] [9bc650122a538aab] login to server success, get run id [9bc650122a538aab], server udp port [0]
2020/05/15 22:34:49 [I] [proxy_manager.go:144] [9bc650122a538aab] proxy added: [ssh]
2020/05/15 22:34:49 [I] [control.go:179] [9bc650122a538aab] [ssh] start proxy success

测试

启动完成后就可以通过 ssh 连接到内网服务器了。

1
ssh -p 6000 enoch@xxx.xx.xxx.xxx

我是平也,这有一个专注Gopher技术成长的开源项目「go home」

要想在终端后台常驻进程,首先想到的是在命令后加 & 符号,来达到隐藏程序在后台的目的,尽管看起来进程已经在后台运行了,实际上终端会话关闭时进程还是会被 kill 掉,这种问题一般是采用搭配 nohup 命令来解决的,nohup 作用是忽略 SIGHUP 信号,而会话关闭时正好发送了该信号给会话内所有运行程序,简而言之,nohup 命令搭配 & 不仅可以在后台运行,还不受会话关闭的影响。

1
$ nohup /bin/cat &

file

那么问题来了,虽然做到了后台运行,也避免了挂断操作带来的影响,但是它避免不了常驻进程自己出现问题,一旦它因自身异常终止了进程,这对黄金搭档就无力回天了。那怎么才能把挂了的常驻进程拉起来呢?这就是我们要讲的主题 Supervisor。

Supervisor 介绍

Supervisor 是专门用来在类 Unix 系统上监控管理进程的工具,发布于 2004 年,虽然名字气势磅礴,但它的志向并不是统筹整个操作系统的进程,而是致力于做一个听话的贴身助理,你只需要告诉它要管理的程序,它就按你的要求监控进程,救死扶伤,保证进程的持续运行。

file

类 Unix 系统就是由 Unix 设计风格演变出的操作系统,除了 Windows 市面上绝大多数系统都是类 Unix 系统。

官方文档介绍 Supervisor 是 C/S 架构体系,它对应的角色分别为 Supervisorctl 和 Supervisord。后者的主要作用是启动配置好的程序、响应 Supervisorctl 发过来的指令以及重启退出的子进程,而前者是 Supervisor 的客户端,它以命令行的形式提供了一系列参数,来方便用户向 Supervisord 发送指令,常用的有启动、暂停、移除、更新等命令。

Supervisor 安装与配置

安装 Supervisor 很简单,在各大操作系统的软件包管理器中都可以直接安装。

1
$ yum install -y supervisor

安装好的 Supervisor 配置文件默认为 /etc/supervisor.conf,如果找不到配置文件可以通过官方命令生成,该配置文件包含了一个空的配置目录 /etc/supervisor.d(不同 OS 可能不一样),只需在该目录添加配置文件即可动态扩展,所以 supervisor.conf 一般不需要做改动。

我们以最简单的 cat 命令为例,cat 命令不加参数会阻塞住等待标准输入,所以很适合做常驻进程的演示。现在创建一个配置文件 cat.ini 到 /etc/supervisor.d/,第一行定义程序的名称,该名称用来做操作的标识,第二行定义命令路径,它才是程序执行的根本命令。

/etc/supervisor.d/cat.ini
1
2
[program:foo]
command=/bin/cat

配置好后,启动 supervisord 服务,注意通过 -c 指定 supervisor 的配置文件。

1
$ supervisord -c /etc/supervisord.conf

当然也可以不指定配置路径,那么它会按以下顺序逐个搜索配置文件:

  • $CWD/supervisord.conf
  • $CWD/etc/supervisord.conf
  • /etc/supervisord.conf
  • /etc/supervisor/supervisord.conf
  • ../etc/supervisord.conf
  • ../supervisord.conf

如果你是通过 Mac OS 安装的 Supervisor,可能从上述目录都找不到配置文件,可以利用官方提供的命令生成配置。

1
$ echo_supervisord_conf > supervisor.conf

这个时候 cat 进程应该已经跑起来了。

1
$ ps aux | grep /bin/cat

杀掉进程,进程 id 会发生变化,证明 supervisor 又把 cat 拉了起来。

1
$ sudo kill 9 <进程ID>

核心配置讲解

配置文件中的选项并不止 command,官方提供了很多配置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[program:name]
command=sh /tmp/echo_time.sh
priority=999
numprocs=1
autostart=true
autorestart=true
startsecs=10
startretries=3
exitcodes=0,2
stopsignal=QUIT
stopwaitsecs=10
user=root
log_stdout=true
log_stderr=true
logfile=/tmp/echo_time.log
logfile_maxbytes=1MB
logfile_backups=10
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=20
stdout_logfile=/tmp/echo_time.stdout.log

下面挑选几个配置简要说明

  • command:要执行的命令
  • priority:执行优先级,值越高就越晚启动,越早关闭
  • numprocs:进程数量
  • autostart:是否与 supervisord 一起启动
  • autorestart:自动重启
  • startsecs:延时启动时间,默认为 10 秒
  • startretries:启动重试次数,默认为 3 次
  • exitcodes:当程序的退出码为 0 或 2 时,重启
  • stopsignal:停止信号
  • stopwaitsecs:延时停止时间,收到停止指令后多久停止
  • user:以哪个用户执行

动态操作子程序

添加新的程序,只需增加配置文件,然后执行 supervisorctl update 即可动态添加新的程序,并不需要重启 supervisord 服务。如果出现 refused connection 的提示,可能是没找到配置文件,需要加上配置选项。

1
2
$ supervisorctl update
foo1: added process group

删除同理,remove 时会先将进程关闭,再从列表中移除。

1
2
foo1: stopped
foo1: removed process group

如果需要单独停止某个程序,可以使用 stop 命令,stop 后跟的是 program 名称。

1
2
$ supervisorctl stop foo
foo: stopped

当然还可以通过 stop all 命令更加暴力的停止所有进程。

1
2
3
$ supervisorctl stop all
foo: stopped
foo1: stopped

反之亦然,启动进程只需要将 stop 改为 start。

1
$ supervisorctl start all

连接到某个进程

1
$ supervisorctl fg <program 名称>

重启 supervisord

1
$ supervisorctl reload

动态加载 supervisor.conf

1
$ supervisorctl reread

查看所有进程运行状况

1
$ supervisorctl status

Web 界面操作

官方提供了界面操作方式,需要在 supervisor.conf 中去掉 inet_http_server 的注释。

supervisor.conf
1
2
3
4
[inet_http_server]         ; inet (TCP) server disabled by default
port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface
username=user ; default is no username (open server)
password=123 ; default is no password (open server)

重启 supervisord。

1
$ supervisorctl reload

访问 http://localhost:9001 可以看到 supervisor 的操作界面。

file

今天文章就到这里,想要了解更多,欢迎查看官方文档

最近机缘巧合的购置了域名和服务器,不用实在是浪费,再加上一直没有属于自己的个人网站,所以打算用hexo在服务器上玩一下,这样也就不用再纠结用Github pages还是Gitee pages了。当然,今天的主题并不是博客搭建,而是如何利用Github的钩子,将博客代码部署到服务器上。

毕竟Github的钩子已经历史悠久了,网上有很多开源项目可以拿来用,所以我并没有造轮子,而是去找了5K star的开源Go项目webhook,这个工具的作用是接收Github仓库的变动通知,然后调用你配置好的shell脚本,脚本可以写上代码拉取的命令或是编译的操作等,具体根据个人需求而定。简而言之,它只起着拉通Github与你服务器的作用。

file

webhook工具安装

因为webhook是Go语言开发的,所以要先安装Go语言。

1
yum install -y golang

然后就可以用go命令安装webhook了。

1
go get github.com/adnanh/webhook

命令安装位置可以通过go env查看,GOPATH就是命令安装路径,比如我的命令就安装在/root/go/bin/webhook。

1
2
3
4
5
go env
...
GOOS="linux"
GOPATH="/root/go"
...

生成ssh key

在编写脚本之前确保服务器有权限拉取github代码,如果已经做了配置可跳过本节去看部署脚本编写。ssh key是代码托管平台(github、gitee、coding、gitlab等)鉴别你是否有权拉取代码的身份标识,生成只需一行命令和一路回车就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ssh-keygen

Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:M6sCf/J/hOu3zLxMkFUVmv3iWIa30CfbxiWqmWCt1YE root@iZwz96y36tk2ecnykzituxZ
The key's randomart image is:
+---[RSA 2048]----+
| ..o. |
| . o |
| . o |
| . o . |
| E S. . |
| . . ..Oo .. |
| oo o ==Boo . |
| .++.+o#== . |
| .=*+=+@o |
+----[SHA256]-----+

生成后可通过cat ~/.ssh/id_rsa.pub命令查看,最后将key加入github即可,加法不再赘述,请自行谷歌。

1
2
3
cat ~/.ssh/id_rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQHBAAABAQCv7LGVJUFdcLL+HZyRFTQIQCdre61Gch76lDVpmWSX9BGGRU3iQS7EU5qApFn1VSvt+yf4rMt2LEkuxGCm1wIyBKZ6LYDViZBeTAfx4BcM1mcpxOX6I/+r07mQ4llTz+poQB1Zp9Y60uk0tbGOVWlCoDBEvf9qeEnQ0qEczEkv7wcawV6pVhlXjFKZgq0EOQbCYoWMvPUl+dwDbTcl/h+7At1nlgfF7IuRHlKf18qvgnTRT2wpiuz4pWdoAi8LcY1JiR1z5OB0oCJ2euhyDND39G2NxZRS1FIVdgCEvioHtdoHOSoWBlcSj0fLFSnscBfRBrCd7yhOP7fFKfrowHMj root@iZwz96y36tk2ecnykzituxZ

部署脚本编写

该shell脚本的主要目的是从github拉取代码,脚本内容很简单,只做了目录的简要判断,代码目录存在则更新,不存在则克隆仓库,工作目录和仓库名称、地址请换成自己的。

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

cd /home/www/website

if [ ! -d "go-home" ]; then
git clone https://github.com/pingyeaa/go-home.git
fi

cd go-home
git pull

webhook配置与启动

编写配置文件hooks.json,格式如下。

1
2
3
4
5
6
7
[
{
"id": "deploy-webhook",
"execute-command": "deploy.sh",
"command-working-directory": "/home"
}
]
  • id:钩子的id,可自定义
  • execute-command:要执行的脚本名,就是刚才编写的部署脚本
  • command-working-directory:脚本所在目录

完成后通过webhook命令启动,可以看到id为deploy-webhook的配置已经加载了,我们需要注意的是监听的端口和路径,等下要用到。

1
2
3
4
5
6
7
8
9
/root/go/bin/webhook -hooks hooks.json -verbose

[webhook] 2020/04/22 15:18:22 version 2.6.11 starting
[webhook] 2020/04/22 15:18:22 setting up os signal watcher
[webhook] 2020/04/22 15:18:22 attempting to load hooks from hooks.json
[webhook] 2020/04/22 15:18:22 found 1 hook(s) in file
[webhook] 2020/04/22 15:18:22 loaded: deploy-webhook
[webhook] 2020/04/22 15:18:22 serving hooks on http://0.0.0.0:9000/hooks/{id}
[webhook] 2020/04/22 15:18:22 os signal watcher ready
1
http://0.0.0.0:9000/hooks/{id}

Github Webhooks配置

现在服务器已经启动了webhook程序监听9000端口,接下来仅需要告诉Github这个地址和端口就好了。

打开仓库设置页,添加webhook。

配置webhooks,Payload URL就是要通知的地址,把刚才打印出的端口和路径填上即可,其他默认。

file

现在可以提交代码测试了,如果推送失败Github中会有错误提示,同样的,成功不仅在Github中能看到,服务器的打印日志也有记录。

file

file