从前开发一个互联网服务程序,大概可以在一台机器上完成:数据库、应用都在一起。随着业务发展壮大,会把数据库独立出来,以便扩展拆分。然后再把一部分公用业务独立出来扩展,譬如文件存储、缓存等。接着业务也才拆分,比如会员、商品。微服务大行其道,各个团队维护着许多服务、API。这么多服务,前端业务逻辑该怎么接入呢?
如今单一的前端UI也可能是多个团队共同开发的结果。当你在本地开发一个小功能时,甚至会牵扯到多个前端UI/后端API。一个前端页面除了加载自己的资源外,还加载V2/V3的API(为什么会同时存在V1/V2/V3的API),甚至嵌入了另外一个页面(React看起来也不错),该怎么调试这种混合开发呢?cookie都传递不过去,好吧,使用JWT代替,再设置一下跨源资源共享(CORS)。。。但是那些旧的应用怎么办?
当我们启动一个Node.js应用时npm run start,默认监听3000端口。当然我们也可以让它监听80端口。但是当我们多开几个应用时,只好让它们都监听不同端口了,怎么样才能统一端口监听呢?当然Node.js可以通过诸如node-http-proxy来转发这些请求,但是代理转发跟应用业务无关吧?如果是其他编程语言呢?都重复这些代理配置/开发吗?Don’t Repeat Yourself。
Faas也日渐流行,比如AWS Lambda, 强调仅仅专注某个功能,根据事件驱动进行计算,那么每个服务也要开发一整套路由分发/认证/日志吗?
这些问题可以使用反向代理来解决,提供统一的服务入口,对前端/客户端隐藏背后的细节,最简单的当然是Nginx。Nginx监听来自某个端口(比如80)的请求,然后根据不同的来源/端口/域名/url分发给不同的后端服务器。这看起来跟云服务厂商各自开发的负载均衡器差不多,比如AWS ELB。
在本地开发的时候,我们当然不会使用ELB来解决。直接使用Nginx当然也没啥问题,编辑一下配置文件,重启应用。但是如果有个UI就更好了,如果还有API就非常好了-这样就能方便的在线注册/更改路由而不需要重启服务器,在这个快速发展、弹性开发的年代更需要这个能力。
Kong是一个基于OpenResty的网关服务器,可以进行路由(转发/负载),插件(日志/认证/监控)管理,并提供RESTful API。而OpenResty 是一个基于Nginx与Lua的高性能Web平台,使用Lua来构建动态网关。听起来像是在Nginx上面编程,这跟在Apache上面使用PHP模块进行编程有什么区别?最大的区别在于,这里的编程对象是Nginx(或者公共模块),扩展Nginx能力,比如负载均衡/日志/认证/监控,而不是输出web页面/业务逻辑。这些东西抽出来以后,就不需要每个模块再重复开发了,比如认证/安全。
在本地可以使用docker来运行Kong服务,麻烦在于数据库迁移工作。通常部署Kong需要几个步骤,比如初始化数据库,迁移升级等等。网上的配置大都过时了,这里可以使用官方提供docker-compose.yml来做数据库工作,并且使用Konga作为管理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 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 130 131 132 133 134 135 136 137 | ➜ kong ls POSTGRES_PASSWORD data docker-compose.yml ➜ kong ls data postgresql ➜ kong cat POSTGRES_PASSWORD kong ➜ kong cat docker-compose.yml version: '3.7' volumes: kong_data: {} networks: kong-net: external: false services: kong-migrations: image: "${KONG_DOCKER_TAG:-kong:latest}" command : kong migrations bootstrap depends_on: - db environment: KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-kong} KONG_PG_HOST: db KONG_PG_USER: ${KONG_PG_USER:-kong} KONG_PG_PASSWORD_FILE: /run/secrets/kong_postgres_password secrets: - kong_postgres_password networks: - kong-net restart: on-failure deploy: restart_policy: condition: on-failure kong-migrations-up: image: "${KONG_DOCKER_TAG:-kong:latest}" command : kong migrations up && kong migrations finish depends_on: - db environment: KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-kong} KONG_PG_HOST: db KONG_PG_USER: ${KONG_PG_USER:-kong} KONG_PG_PASSWORD_FILE: /run/secrets/kong_postgres_password secrets: - kong_postgres_password networks: - kong-net restart: on-failure deploy: restart_policy: condition: on-failure kong: image: "${KONG_DOCKER_TAG:-kong:latest}" user: "${KONG_USER:-kong}" depends_on: - db environment: KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_ADMIN_ERROR_LOG: /dev/stderr KONG_ADMIN_LISTEN: '0.0.0.0:8001' KONG_CASSANDRA_CONTACT_POINTS: db KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-kong} KONG_PG_HOST: db KONG_PG_USER: ${KONG_PG_USER:-kong} KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_PG_PASSWORD_FILE: /run/secrets/kong_postgres_password secrets: - kong_postgres_password networks: - kong-net ports: - "8000:8000/tcp" - "127.0.0.1:8001:8001/tcp" - "8443:8443/tcp" - "127.0.0.1:8444:8444/tcp" healthcheck: test : [ "CMD" , "kong" , "health" ] interval: 10s timeout: 10s retries: 10 restart: on-failure deploy: restart_policy: condition: on-failure db: image: postgres:9.5 environment: POSTGRES_DB: ${KONG_PG_DATABASE:-kong} POSTGRES_USER: ${KONG_PG_USER:-kong} POSTGRES_PASSWORD_FILE: /run/secrets/kong_postgres_password secrets: - kong_postgres_password healthcheck: test : [ "CMD" , "pg_isready" , "-U" , "${KONG_PG_USER:-kong}" ] interval: 30s timeout: 30s retries: 3 restart: on-failure deploy: restart_policy: condition: on-failure stdin_open: true tty : true networks: - kong-net volumes: - /Users/xxxx/docker/kong/data/postgresql : /var/lib/postgresql/data konga: image: pantsel /konga environment: TOKEN_SECRET: channing.token DB_ADAPTER: postgres DB_HOST: db DB_USER: ${KONG_PG_USER:-kong} DB_PASSWORD: kong DB_DATABASE: ${KONG_PG_DATABASE:-kong} ports: - 1337:1337 networks: - kong-net depends_on: - db secrets: kong_postgres_password: file : . /POSTGRES_PASSWORD |
运行docker-composer up就可以看到
1 2 3 4 5 | ➜ kong docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 20ebe7885c0d kong:latest "/docker-entrypoint.…" 2 hours ago Up 11 seconds (healthy) 0.0.0.0:8000->8000 /tcp , 127.0.0.1:8001->8001 /tcp , 0.0.0.0:8443->8443 /tcp , 127.0.0.1:8444->8444 /tcp kong_kong_1 4a7a39c863ae pantsel /konga "/app/start.sh" 2 hours ago Up 11 seconds 0.0.0.0:1337->1337 /tcp kong_konga_1 aa732758fc51 postgres:9.5 "docker-entrypoint.s…" 2 hours ago Up 11 seconds (health: starting) 5432 /tcp kong_db_1 |
访问http://127.0.0.1:1337/即可以进入Konga 管理界面了。8000/8443端口是需要监听转发的端口,8001/8444则是Kong RESTFUl管理API的端口。这里也可以把8000/8443改为80/443,这样访问的时候就可以直接使用域名/localhost而不必加端口了。
注册登录进去后首先要添加Kong RESTFUl管理API的地址,这里使用的是docker环境,IP是动态分配的,所以使用链接名就可以了
连接上Kong API后可以看到dashboard列出了支持的插件
Kong里面的管理对象是service,路由转发/插件都是围绕service展开的,添加一个service
在它上面添加一个路由,这里我们在服务只监听以/api开头的url,并转发到后端服务器去
注意这里在route里面的path里面也需要添加/api,否则转发的时候会出错(多拼api)。最简单就是service监听不指定path,在route里指定。一个service下面可以有多个转发路由,每个可以独立管理、设置超时等。
还可以为每个service绑定不同的插件进行处理,比如认证/安全/日志等等,这样就不用在不同的系统里面重复开发这些功能,使得各个团队更加专注也本业务开发
有效插件还可以进行流量限制、请求头/响应头修改等等。这些插件的功能需要基于consumer来开发管理,功能可以非常强大,详细可以参考文档。
Kong还有一项功能Upstream配置,与Nginx的ngx_http_upstream_module差不多,可以作为负载均衡、流量分发控制使用。可以用命令行测试基于hostname的路由,
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 | ➜ kong curl -i -X GET \ --url http: //localhost :8000/ \ --header 'Host: dev1.example.com' HTTP /1 .1 404 Not Found Date: Tue, 27 Oct 2020 08:41:58 GMT Content-Type: application /json ; charset=utf-8 Connection: keep-alive Content-Length: 48 X-Kong-Response-Latency: 1 Server: kong /2 .1.4 { "message" : "no Route matched with those values" } ➜ kong curl -i -X GET \ --url http: //localhost :8000/ \ --header 'Host: dev.example.com' HTTP /1 .1 200 OK Content-Type: text /html ; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Date: Tue, 27 Oct 2020 08:42:21 GMT Server: Apache /2 .2.15 (CentOS) X-Powered-By: PHP /5 .6.40 Set-Cookie: PHPSESSID=0uc3aoc735j21sk3ni5s72vjc1; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE Access-Control-Allow-Headers: X-CSRF-Token,Authorization,X-Accept-Charset,X-Accept,Content-Type X-Kong-Upstream-Latency: 8397 X-Kong-Proxy-Latency: 0 Via: kong /2 .1.4 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" > ... |
作为一个API网关,Kong能做的很多。如果只是在本地简单的做反向代理,可以使用nginx-proxy-manager,这也是一个基于Nginx开发的网关,根据WEB UI动态生成Nginx配置文件,然后执行/usr/sbin/nginx -s reload生效。有些参数不能通过UI配置(比如超时设置),可以直接写Nginx配置,upstream则支持直接转发tcp请求,支持websockets代理转发,甚至集成了Let’s encrypt自动申请SSL证书(需要验证域名)
本地同样可以使用docker跑起来,默认监听80/443端口,81端口即管理界面
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 | ➜ nginx-proxy-manager ls -lah total 16 drwxr-xr-x 6 xxxx domain users 192B Sep 29 10:24 . drwxr-xr-x 15 xxxx domain users 480B Sep 29 09:15 .. -rw-r--r-- 1 xxxx domain users 2.3K Sep 29 09:42 config.json drwxr-xr-x 8 xxxx domain users 256B Sep 29 09:31 data -rw-r--r-- 1 xxxx domain users 740B Sep 29 10:24 docker-compose.yml drwxr-xr-x 3 xxxx domain users 96B Oct 27 16:38 letsencrypt ➜ nginx-proxy-manager cat docker-compose.yml version: "3" services: app: image: 'jc21/nginx-proxy-manager:latest' restart: always ports: # Public HTTP Port: - '80:80' # Public HTTPS Port: - '443:443' # Admin Web Port: - '81:81' # TCP Forward Example: - '8022:8022' volumes: # Make sure this config.json file exists as per instructions above: - . /config .json: /app/config/production .json - . /data : /data - . /letsencrypt : /etc/letsencrypt depends_on: - db db: image: jc21 /mariadb-aria restart: always environment: MYSQL_ROOT_PASSWORD: 'npm' MYSQL_DATABASE: 'npm' MYSQL_USER: 'npm' MYSQL_PASSWORD: 'npm' volumes: - . /data/mysql : /var/lib/mysql ➜ nginx-proxy-manager docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3dd58e9cff1f jc21 /nginx-proxy-manager :latest "/init" 4 weeks ago Up 7 days (healthy) 0.0.0.0:80-81->80-81 /tcp , 0.0.0.0:443->443 /tcp , 0.0.0.0:8022->8022 /tcp nginx-proxy-manager_app_1 a033151b28ed jc21 /mariadb-aria "/scripts/run.sh" 4 weeks ago Up 7 days 3306 /tcp nginx-proxy-manager_db_1 |
nginx-proxy-manager会将配置写在data目录下面,可以直接编辑这些文件,Nginx reload之后便会生效,可以看到这些文件,具体的加载规则参考文档
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 | ➜ nginx-proxy-manager ls data /nginx dead_host default_host default_www dummycert.pem dummykey.pem proxy_host redirection_host stream temp ➜ nginx-proxy-manager cat data /nginx/proxy_host/1 .conf # ------------------------------------------------------------ # dev.example.com # ------------------------------------------------------------ server { set $forward_scheme http; set $server "192.168.33.14" ; set $port 80; listen 80; listen [::]:80; server_name dev.example.com; access_log /data/logs/proxy_host-1 .log proxy; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; send_timeout 600; location /Login { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass https: //login .example.com:443; } location /api/v2 { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http: //192 .168.33.14:9070; } location /api/v3 { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass https: //dev .ops.example.com:443; } location /Chat { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass https: //dev .ops.example.com:443; } location /Catalog { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http: //192 .168.33.1:3000; } location /static { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http: //192 .168.33.1:3000; } location /css { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http: //192 .168.33.1:3000; } location / { # Proxy! include conf.d /include/proxy .conf; } # Custom include /data/nginx/custom/server_proxy [.]conf; } ➜ nginx-proxy-manager cat data /nginx/stream/1 .conf # ------------------------------------------------------------ # 8022 TCP: 1 UDP: 0 # ------------------------------------------------------------ server { listen 8022; listen [::]:8022; proxy_pass 192.168.33.14:22; # Custom include /data/nginx/custom/server_stream [.]conf; include /data/nginx/custom/server_stream_tcp [.]conf; } |
在docker-compose.yml里面设置一下upstream监听转发的端口,就可以通过8022端口访问192.168.33.14的22端口了
1 2 3 4 5 6 | ➜ nginx-proxy-manager telnet 127.0.0.1 8022 Trying 127.0.0.1... Connected to localhost. Escape character is '^]' . SSH-2.0-OpenSSH_5.3 ^C^C^C^C^C^C^CConnection closed by foreign host. |
不论是Kong还是nginx-proxy-manager均有提供API,极大的增强服务网关的可编程性,为动态上线/弹性扩展/自动化运维提供了便利。
参考链接:
从IaaS到FaaS—— Serverless架构的前世今生
聊一聊微服务网关 Kong
KONG网关 — KongA管理UI使用
云原生架构下的 API 网关实践: Kong (二)
微服务 API 网关 -Kong 详解
Creating a web API with Lua using Nginx OpenResty
Nginx基于TCP/UDP端口的四层负载均衡(stream模块)配置梳理
Nginx支持TCP代理和负载均衡-stream模块
聊聊 API Gateway 和 Netflix Zuul
Envoy 是什么?