APISIX入门到上手

1:APISIX是什么

Apache APISIX是Apache软件基金会下的云原生API网关,它具有动态,实时,高性能等特点,提供了负载均衡,动态上游,灰度发布(金丝雀发布),服务熔断,限速,防御攻击,身份认证,可观测性等丰富的流量管理功能,我们可以使用APISIX来处理传统的南北流量,也可以处理服务之间的东西流量,同时,它也支持作为kubernetes Ingress Controller来使用

APISIX是基于Nginx和Etcd与传统的API网关相比,APISIX具有动态路由和热加载插件功能,避免了配置之后的reload操作,同时APISIX支持HTTP(S),HTTP2,Dubbo,QUIC,MQTT,TCP/UDP等多种协议,还内置了Dashboard,提供强大且灵活的界面,同样也提供了丰富的插件支持功能,并且还可以让用户自定义插件

主要特点:
1:多平台支持:APISIX提供了多平台支持方案,不仅支持裸机运行,也支持在Kubernetes中使用,还与AWS Lambda,Azure Function,Lua函数和Apache OpenWhisk等云服务集成
2:全动态能力:APISIX支持热加载,这意味着我们不需要重启服务就可以更新APISIX的配置,在Ingress-Nginx的实现是基于Lua
3:精细化路由:APISIX支持使用Nginx内置变量作为路由匹配条件,我们可以自定义匹配函数来过滤请求,匹配路由
4:运维友好:APISIX支持与以下工具进行集成:HashiCorp Vault,Zipkin,Apache SkyWalking,Consul,Nacos,EureKa。通过APISIX Dashbaord,运维人员可以友好并直观的配置APISIX
5:多语言插件支持:APISIX支持多种开发语言进行插件开发,开发人员可以选择擅长语言的SDK开发自定义插件

2:APISIX安装

那么我们为了体验APISIX,我们这里直接使用Docker启动一个APISIX来体验一下,这个在官方是给你仓库的

https://github.com/apache/apisix-docker.git

[root@localhost ~]# git clone https://github.com/apache/apisix-docker.git
Cloning into 'apisix-docker'...
remote: Enumerating objects: 1947, done.
remote: Counting objects: 100% (120/120), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 1947 (delta 62), reused 89 (delta 41), pack-reused 1827
Receiving objects: 100% (1947/1947), 373.54 KiB | 835.00 KiB/s, done.
Resolving deltas: 100% (1039/1039), done.

[root@localhost ~]# cd apisix-docker/example/

# 这里给大家提一下,Docker在23版本直接集成了compose,也就是说我们不再需要去安装compose了

[root@localhost example]# docker compose -p docker-apisix up -d
....
[+] Running 9/9
 ✔ Network docker-apisix_apisix                Created                                                 0.1s 
 ✔ Volume "docker-apisix_etcd_data"            Created                                                 0.0s 
 ✔ Container docker-apisix-apisix-dashboard-1  Started                                                 2.5s 
 ✔ Container docker-apisix-web2-1              Started                                                 2.1s 
 ✔ Container docker-apisix-etcd-1              Started                                                 1.3s 
 ✔ Container docker-apisix-web1-1              Started                                                 1.8s 
 ✔ Container docker-apisix-prometheus-1        Started                                                 2.7s 
 ✔ Container docker-apisix-grafana-1           Started                                                 2.0s 
 ✔ Container docker-apisix-apisix-1            Started                                                 3.1s
 
# API测试访问 
[root@localhost example]# curl 10.0.0.15:9080
{"error_msg":"404 Route Not Found"}
# 出现404是因为我们还没有路由,所以才会出现404

我们直接访问:http://ip:9000,可以访问到Dashboard

image

3:APISIX功能测试

我们接下来主要是针对APISIX的几个主要概念和组件简单了解一下
1:上游:Upstream也称之为上游,上游是对虚拟主机的抽象,即应用层服务或节点的抽象
上游的作用是按照配置规则对服务节点进行负载均衡,它的地址信息可以直接配置到路由或服务上,当多个路由或服务引用同一个上游时,可以通过创建上游对象,在路由或服务中使用上游的ID方式引用上游,减轻维护压力

2:路由:Router也称为路由,时APISIX中最基础和最核心的资源对象。
APISIX可以通过路由定义规则来匹配客户端请求,根据匹配结果加载并执行相应的插件,最后把请求转发给指定的上游服务,路由中主要包含三部分内容:匹配规则,插件配置和上游信息。

3:服务:Service也称之为服务,时类似API的抽象(也可以理解为是一组Router的对象),它通常与上游服务抽象是一一对应的,Router与Service之间,通常是N:1的关系

4:消费者:Consumer是某类服务的消费者,需要与用户认证配合才可以使用,当不同的消费者请求同一个API时,APISIX会根据当前请求的用户信息,对应不同的Plugin或Upstream,如果Route,Service,Consumer和Plugin Config都绑定了相同的插件,只有消费的插件配置会生效,插件配置的优先级高低顺序是:Consumer > Route > Plugin Config > Service
对于API网关而言,一般情况可以通过请求域名,客户端IP地址等字段识别某某类请求方,然后进行插件过滤转发请求到指定上游,但有时候该方式达不到用户需求,因此APISIX支持了Consumer对象。

5:插件:Plugin也称之为插件,它是扩展APISIX应用层能力的关键机制,也是在使用APISIX时最常用的资源对象,插件主要是在HTTP请求或响应生命周期期间执行的,针对请求的个性化策略,插件可以与路由,服务或消费者绑定

# 如果路由,服务,插件配置或消费者都绑定了相同的插件,则只有一份插件配置会生效,上面我们将Consumer也提到了,同时在插件执行的过程中也会涉及6个阶段,分别是:rewrite,access,before_proxy,header_filter,body_filter和log。

6:Admin API:APISIX提供了强大的Admin API和Dashboard供用户使用,Admin API是一组用于配置Apache APSXI路由,上游,服务,SSL证书等功能的RESTFul API

我们可以通过Admin API来增删改查资源,同时得益于APISIX的热加载功能,资源配置完成后APISIX会自动更新配置,无需重启服务

4:APISIX使用

那么我们使用前就来创建第一条路由,这里我们直接使用Admin API的方式创建

curl "http://10.0.0.15:9180/apisix/admin/routes/1" -X PUT -d '
{
  "methods": ["GET"],        # 请求API的方法
  "host": "kudevops.cn",     # 请求API的时候需要的Host
  "uri": "/anything/*",           # 请求API的时候携带的路由
  "upstream": {              # 上游服务池
    "type": "roundrobin",    # 轮询算法
    "nodes": {               # 服务池
      "httpbin.org:80": 1    # 服务地址与ID
    }
  }
}' -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'

# 注意这里的Key安装APISIX的时候需要更改,否则会出现谁安装APISIX都是这个Key然后就会被恶意调用API的问题,它的配置在APISIX的conf里面

image

我这里使用了APIPOST来调用了一下API,然后看返回结果,我们可以看到状态码是201,也就说明了我们的路由已经创建好了。

那么我们来请求一下,模拟一下请求

[root@localhost example]# curl http://10.0.0.15:9080/anything/k8s?train=1 --header 'Host: kudevops.cn'
{
  "args": {
    "train": "1"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "kudevops.cn", 
    "User-Agent": "curl/7.76.1", 
    "X-Amzn-Trace-Id": "Root=1-645ca158-42b0cb5a032db0a228271860", 
    "X-Forwarded-Host": "kudevops.cn"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "10.0.0.15, 101.228.215.115", 
  "url": "http://kudevops.cn/anything/k8s?train=1"
}


可以看到请求路过了网关,然后直接访问到了上游,这个上游是一个公开的API测试接口,谁都可以用,有兴趣大家可以去玩一玩,这就是第一种创建路由的方式,第二种方式就是我们可以先去创建上游,然后再将上游添加到路由内,因为这个我们前面讲过的,

image

可以看到创建了一个上游的服务,返回信息中给了我们一个id,我们去创建路由的时候就可以通过这个id去绑定给这个上游了,我们再次去创建一个Route

image

可以看到,这个时候我们就不需要自己指定上游了,直接指定上游组的ID就可以了,然后再次测试一下

[root@localhost example]# curl http://10.0.0.15:9080/get --header 'Host: kudevops.cn'
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "kudevops.cn", 
    "User-Agent": "curl/7.76.1", 
    "X-Amzn-Trace-Id": "Root=1-645ca4b8-1f99db692e45eeed0a7d19ac", 
    "X-Forwarded-Host": "kudevops.cn"
  }, 
  "origin": "10.0.0.15, 101.228.215.115", 
  "url": "http://kudevops.cn/get"
}

# 可以看到这个时候API也是没问题的,这个就是我们操作Admin API可以做到的,其次我们就是要介绍到Dashboard了

5:APISIX控制台使用

image

控制台的账号密码也是在/root/apisix-docker/example下面是有的,默认是

账号:admin
密码:admin

image

image
image

image
image
image
image
image
image
image

[root@localhost example]# curl http://10.0.0.15:9080/anything/k8s?train=1 --header 'Host: kudevops.cn'
{
  "args": {
    "train": "1"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "kudevops.cn", 
    "User-Agent": "curl/7.76.1", 
    "X-Amzn-Trace-Id": "Root=1-645ccb0a-6b898329170dbcab2be0bbed", 
    "X-Forwarded-Host": "kudevops.cn"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "10.0.0.15, 101.228.215.115", 
  "url": "http://kudevops.cn/anything/k8s?train=1"
}

# 其实和我们去请求API基本效果是一样的,那么我们接下来就来介绍几个APISIX的比较有代表性的插件吧

6:APISIX插件使用

如果想利用APISIX实现身份验证,安全性,限流限速和可观测性等功能,可通过添加插件来实现。

6.1:限流限速和安全插件

在很多时候,我们的API并不是处于一个非常安全的状态,它随时可能收到不正常的访问,一旦访问流量突增,可能就会导致你的API发生故障,这个时候我们就可以通过限速来保护API服务,限速非正常访问的请求,对此,我们可以使用如下方式进行:
1:限制请求速度
2:县直单位时间内的请求数
3:延迟请求
4:拒绝客户端请求
5:限制相应数据的速率

APISIX提供了多个内置的限速限流的插件,包括limit-conn,limit-count和limit-req,
1:limit-conn:插件主要用于限制客户端对服务的并发请求数
2:limit-count:插件主要用于在指定时间范围内,限制每个客户端请求的总数
3:limit-req:插件使用漏桶算法限制对用户服务的请求速率

那么我们下面创建一条路由来试试这个功能

curl "http://10.0.0.15:9180/apisix/admin/routes/1" -X PUT -d '
{
  "uri": "/index.html",
  "plugins": {
  	"limit-count": {
  	  "count": 2,
  	  "time_window": 60,
  	  "rejected_code": 503,
  	  "key_type": "var",
  	  "key": "remote_addr"
  	}
  },
  "upstream_id": "1"
}' -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'

image

[root@localhost example]# for i in {1..3};do curl http://10.0.0.15:9080/index.html -I ;done
# 第一次请求,404是因为上游没有这个文件
HTTP/1.1 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 233
Connection: keep-alive
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 1
X-RateLimit-Reset: 60
Date: Thu, 11 May 2023 11:32:29 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.3.0
# 第二次请求
HTTP/1.1 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 233
Connection: keep-alive
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 57
Date: Thu, 11 May 2023 11:32:31 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.3.0
# 第三次,达到插件的阈值,则被插件直接限制
HTTP/1.1 503 Service Temporarily Unavailable
Date: Thu, 11 May 2023 11:32:31 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 269
Connection: keep-alive
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 0
Server: APISIX/3.3.0

6.2:缓存响应

当我们在构建一个API时,肯定希望它能够尽量保持简单和快速,一旦读取相同数据的并发需求添加,可能会面临一些问题,一般我们直接的办法就是引入缓存,当然我们可以在不同层面去进行缓存的。
1:边缘缓存或CDN
2:数据库缓存
3:服务器缓存(API缓存)
4:浏览器缓存

反向代理缓存是另一种缓存机制,通常在API网关内实现,它可以减少对我们端点的调用次数,也可以通过缓存上游的响应来改善对API的请求延迟,如果API Gateway的缓存中有所请求资源的新鲜副本,它就会使用该副本直接满足请求,而不是向端点发出请求,如果没有找到缓存的数据,请求就会转到预定的上游服务(后端服务)

我们这里主要了解的是API网关的缓存,也就是APISIX提供的API缓存,它也可以和其他插件一起使用,目前支持基础的磁盘缓存,也可以在插件配置中指定缓存过期时间或内存容量等

比如我们现在有一个/products的API接口,通常每天只更新一次,而端点每天都会收到重复的数十亿次请求,以获取产品列表数据,现在我们就可以使用APISIX提供一个名为proxy-cache的插件来缓存该接口的响应。

这里我们还是以ID为1的上游作为对象,使用/anything/products来模拟产品接口,直接执行下面的命令来更新路由的插件

curl "http://10.0.0.15:9180/apisix/admin/routes/1" -X PUT -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '
{
  "name": "Route for API Caching",
  "methods": ["GET"],
  "uri": "/anything/*",
  "plugins": {
  	"proxy-cache": {
  	  "cache_key": ["$uri","-cache-id"],
  	  "cache_bypass": ["$arg_bypass"],
  	  "cache_method": ["GET"],
  	  "cache_http_status": [200],
  	  "hide_cache_headers": true,
  	  "no_cache": [
  	    "$arg_test"
  	  ]
  	}
  },
  "upstream_id": "1"
}'

image

这样完成之后我们去测试一下新的路由插件

# 第一次请求
[root@localhost example]# curl http://10.0.0.15:9080/anything/products
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "10.0.0.15", 
    "User-Agent": "curl/7.76.1", 
    "X-Amzn-Trace-Id": "Root=1-645ce8ee-382a80e3238545551d7e9c7e", 
    "X-Forwarded-Host": "10.0.0.15"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "10.0.0.15, 101.228.215.115", 
  "url": "http://10.0.0.15/anything/products"
}


# 再次请求
[root@localhost example]# curl http://10.0.0.15:9080/anything/products -i
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 398
Connection: keep-alive
Date: Thu, 11 May 2023 13:11:50 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.3.0
Apisix-Cache-Status: HIT  # 当HIT的时候证明缓存命中了,也就是证明着我们的数据被缓存了,而且这个时候我们去请求的时候会发现明显的数据变快了

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "10.0.0.15", 
    "User-Agent": "curl/7.76.1", 
    "X-Amzn-Trace-Id": "Root=1-645ce990-0dc0a514782c434e4754341a", 
    "X-Forwarded-Host": "10.0.0.15"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "10.0.0.15, 101.228.215.115", 
  "url": "http://10.0.0.15/anything/products"
}

这就是我们演示的极个别插件,其他的插件大家可以去官网进行查看文档:https://apisix.apache.org/docs/apisix/getting-started/README/

7:APISIX Ingress的使用

前面我们已经学习了APISIX基本使用,同样作为一个API网关,APISIX也支持作为Kubernetes的一个Ingress控制器来进行使用,APISIX Ingress在架构上分成了两部分,一部分是APISIX Ingress Controller,作为控制面它将完成配置管理与分发,另一部分APISIX(代理)负责承载业务流量

image

当客户端发起请求后,到达Apache APISIX后,会直接把相应的业务流量传输到后端(如Service Pod),从而完成转发过程,此过程不需要经过Ingress Controller,这样做可以保证一旦有问题出现,或者进行变更,扩髓容或者迁移处理等,都不会影响用户和业务流量

同时在配置端,用户通过kubectl apply创建资源,可将自定义CRD配置应用到K8S集群,Ingress Controller会持续watch资源变更,来将相应的配置应用到Apache APISIX(通过admin api)

从图上可以看出APISIX Ingress采用了数据面与控制面分离的架构,所以用户可以选择将数据面部署在K8S集群内部或者外部,但是Ingress Nginx是将控制面和数据面放在了同一个Pod中,如果Pod或者控制面出现一点问题,整个Pod就会垮掉,进而影响到业务,这种架构分离,给用户提供了比较方便的部署选择,同时在业务架构调整下,也方便相关数据的迁移与使用

APISIX Ingress 控制器目前支持的核心特性:
1:全动态,支持高级路由匹配,可与Apache APISIX官方插件 & 客户自定义插件进行扩展使用
2:支持CRD,更容易理解声明式配置
3:兼容云原生Ingress对象
4:开箱即用的节点健康检查支持
5:支持基于Pod(上游节点)的负载均衡
6:服务自动注册发现,无惧扩缩容
7:支持gRPC plaintext 与 TCP 4层代理

7.1:安装

我们在Kubernetes集群中来使用APISIX,可以通过Helm Chart来进行安装,首先添加官方的Chart仓库

[root@k-m-1 ~]# helm repo add apisix https://charts.apiseven.com
"apisix" has been added to your repositories
[root@k-m-1 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "apisix" chart repository
Update Complete. ⎈Happy Helming!⎈

由于APISIX的Chart包中包含dashboard和ingress控制器的依赖,我们只需要在values中启用即可安装ingress控制器了

# 下载apisix的Chart包并解压
[root@k-m-1 ~]# helm pull apisix/apisix --untar
[root@k-m-1 ~]# cd apisix/

然后我们创建一个values用于安装apisix
# apisix-values.yaml
apisix:
  enabled: true
gateway:
  type: NodePort
  http:
    enabled: true
    servicePort: 80
    containerPort: 9080
  tls:
    enabled: true  # 启用TLS
    servicePort: 443
    containerPort: 9443
etcd:
  enabled: true
  replicaCount: 1
  persistence:
    enabled: true
    storageClass: managed-nfs-storage
dashboard:
  enabled: true
  config:
    conf:
      etcd:
        endpoints:
        - apisix-etcd:2379
        prefix: "/apisix"
        username: admin
        password: admin
ingress-controller:
  enabled: true
  config:
    apisix:
      serviceName: apisix-admin
      serviceNamespace: apisix
# 更多的自定义配置大家可以自己去研究它的values

[root@k-m-1 ~]# helm upgrade --install apisix ./apisix -f ./apisix/apisix-values.yaml -n apisix --create-namespace 
Release "apisix" does not exist. Installing it now.
NAME: apisix
LAST DEPLOYED: Thu May 11 22:43:36 2023
NAMESPACE: apisix
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace apisix -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway)
  export NODE_IP=$(kubectl get nodes --namespace apisix -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

[root@k-m-1 ~]# kubectl get pod,svc -n apisix 
NAME                                             READY   STATUS    RESTARTS      AGE
pod/apisix-5fbd65f889-djpkh                      1/1     Running   0             93s
pod/apisix-dashboard-f5cfd8d55-q5ffz             1/1     Running   3 (30s ago)   93s
pod/apisix-etcd-0                                1/1     Running   0             93s
pod/apisix-ingress-controller-5dbdb8c8d8-lkbl5   1/1     Running   0             93s

NAME                                TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
service/apisix-admin                ClusterIP   10.96.2.97    <none>        9180/TCP                     93s
service/apisix-dashboard            ClusterIP   10.96.1.213   <none>        80/TCP                       93s
service/apisix-etcd                 ClusterIP   10.96.3.88    <none>        2379/TCP,2380/TCP            93s
service/apisix-etcd-headless        ClusterIP   None          <none>        2379/TCP,2380/TCP            93s
service/apisix-gateway              NodePort    10.96.2.117   <none>        80:32150/TCP,443:32582/TCP   93s
service/apisix-ingress-controller   ClusterIP   10.96.0.86    <none>        80/TCP                       93s

# 可以看到已经部署好了,我们可以访问http://<node-ip>:32424访问到API了
[root@k-m-1 ~]# curl 10.0.0.11:32150
{"error_msg":"404 Route Not Found"}

# 这个时候可以看到和我们前面Docker部署时没有路由时的访问时一模一样的。
[root@k-m-1 ~]# kubectl get crd | grep apisix
apisixclusterconfigs.apisix.apache.org                2023-05-11T14:43:34Z
apisixconsumers.apisix.apache.org                     2023-05-11T14:43:34Z
apisixpluginconfigs.apisix.apache.org                 2023-05-11T14:43:34Z
apisixroutes.apisix.apache.org                        2023-05-11T14:43:34Z
apisixtlses.apisix.apache.org                         2023-05-11T14:43:34Z
apisixupstreams.apisix.apache.org                     2023-05-11T14:43:34Z

# 这是APISIX安装好后带的CRD,这几个CRD我们基本上都是认识的,比如上游,路由,插件配置,消费者,TLS等,那么我们来创建一个APISIX的CRD将dashboard暴露出去
# apisix-dashboard.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: dashboard
  namespace: apisix
spec:
  http:
  - name: root
    match: 
      hosts:
      - apisix.kudevops.cn
      paths:
      - "/*"
    backends:
    - serviceName: apisix-dashboard
      servicePort: 80
[root@k-m-1 ~]# kubectl apply -f apisix-dashboard.yaml 
apisixroute.apisix.apache.org/dashboard created
[root@k-m-1 ~]# kubectl get apisixroutes.apisix.apache.org -n apisix 
NAME        HOSTS                    URIS     AGE
dashboard   ["apisix.kudevops.cn"]   ["/*"]   13s

可以看到这里就创建好了关于APISIX的配置了,然后我们可以将域名解析给APISIX Gateway的节点上然后通过APISIX Gateway去访问dashboard服务了

image

image

可以看到我们的路由就在这儿了,那么这里我们需要解释一下,我们这里的请求是到了APISIX,而并非到了Ingress Controller,这个是非常重要的,因为我们前面说了APISIX是分离架构的,所以Ingress Controller和服务是分开的,所以我们可以不使用Ingress Controller也可以暴露服务,但是这个时候我们只能用APISIX自己的CRD去暴露API服务。

7.2:URL Rewrite

同样我们也来介绍下如何使用APISIX实现URL Rewrite的操作,同样的我们可以去部署一个Demo通过ApisixRoute对象来配置服务路由
# apisix-rewrite.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - name: http
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    targetPort: http
  type: ClusterIP
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: nginx
spec:
  http:
  - name: root
    match:
      hosts:
      - nginx.kudevops.cn
      paths:
      - "/*"
    backends:
    - serviceName: nginx
      servicePort: 80
[root@k-m-1 ~]# kubectl get pod,svc,apisixroutes
NAME                           READY   STATUS    RESTARTS      AGE
pod/jenkins-67585b7ccf-tvbdn   1/1     Running   3 (26h ago)   29h
pod/nginx-586b477ddc-l7p68     1/1     Running   0             15s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                          AGE
service/jenkins      NodePort    10.96.0.81    <none>        8080:30008/TCP,50000:30030/TCP   29h
service/kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP                          10d
service/nginx        ClusterIP   10.96.3.104   <none>        80/TCP                           15s

NAME                                  HOSTS                   URIS     AGE
apisixroute.apisix.apache.org/nginx   ["nginx.kudevops.cn"]   ["/*"]   15s

image

这就是基础的资源创建,它代替了Ingress这个资源,那么我们想一下,我们如果有一个域名是ops.kuevops.cn/xxx,后面的xxx是我们的运维平台比如
http://ops.kudevops.cn/jenkins
http://ops.kudevops.cn/gitlab
http://ops.kudevops.cn/harbor
http://ops.kudevops.cn/kubesphere
http://ops.kudevops.cn/jumpserver
.....

这些我们该怎么做呢,这个时候我们肯定需要涉及到一个重写,那么重写怎么去做呢?我们拿这个Nginx来做一下试试
这里我们需要用到一个插件就是proxy_rewrite,如果我们没有重写的话,比如我们请求了http://ops.kudevops.cn/jenkins,那么我们传到上游的uri就是/jenkins,但是很明显后端的Jenkins是不会有这个路由去处理请求,所以我们需要将这个uri处理一下,重写成后端Jenkins能处理的请求,那么这个就是proxy_rewrite的作用了,首先我们来了解一下需求,我们请求http://ops.kudevops.cn/nginx的时候我们需要将这个/nginx的uri转发给后端nginx的/去处理,这个时候我们来看看如何处理它。

相关文档:https://apisix.apache.org/docs/apisix/plugins/proxy-rewrite/
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: nginx
spec:
  http:
  - name: root
    match:
      hosts:
      - nginx.kudevops.cn
      paths:
      - "/*"
    plugins:
    - name: proxy-rewrite
      enable: true
      config:
        regex_uri: ["^/nginx(/|$)(.*)", "/$2"]  # 这里就是一个正则表达是,不熟悉的话需要去学一学
    backends:
    - serviceName: nginx
      servicePort: 80
[root@k-m-1 ~]# kubectl apply -f apisix-rewrite.yaml 
deployment.apps/nginx unchanged
service/nginx unchanged
apisixroute.apisix.apache.org/nginx configured

image

那么根据uri我们可以看到是不是已经实现了重写,我们访问
http://nginx.kudevops.cn/nginx的时候这个请求直接转给了nginx的/去处理请求了,否则后端nginx请求拿到的转发应该是/nginx这个URL,当然我们也可以去看看Nginx的日志看看是不是真的拿到的是/的请求


100.79.21.66 - - [11/May/2023:20:02:42 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35" "100.114.94.128"

可以看到转发过来的日志是这样子的,从GET后面看到路径的确是 /,那么也就说明了我们的重写是生效的,并且Nginx也处理了这个请求了,但是这里有个问题哈,当我们还有一些静态资源比如
/static/xxx
/css/xxx
/js/xxx
/image/xxx
....
有如上信息的时候可能我们需要在paths内多加几条path,这样才可以一起带着这些路径重写,当然了实现这个功能还可以使用redirect插件来实现,这个插件就是我们常说的重定向了。

插件:https://apisix.apache.org/zh/docs/apisix/plugins/redirect/

它和重写的基本使用差不多是一样的,我们在Nginx都听说过强转吧,也就是HTTP转HTTPS,那么这个插件就可以实现这个功能

7.3:TLS

我们可以通过上面讲的redirect插件中的http_to_https来将HTTP请求转到HTTPS请求上,但是我们现在并没有对我们的nginx.kudevops.cn配置https证书,这里我们需要使用ApisixTls对象来进行证书管理,当然我们可以先使用openssl生成一个自签证书

[root@k-m-1 ssl]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginx.kudevops.cn"
...+..+....+...+............+...+...+..+...+.......+...+..+.......+.........+..+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*....+.......+..+.+...........+..........+.....+.+..+......+....+..+...+......+....+......+..+...............+.......+........+.......+........+...+.+.........+...........+.+.....+...+....+.....+.......+...........+...+.......+.....+.......+..+...+...+...+.+..............+....+.................+.+..+...+...+....+.................+.+.....+...............+....+...+...........+....+......+.....+....+..+.........+......+...+.+..+.......+...+..+......+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.+..+....+...+...+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.....+....+..+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.......+..+.+.....+.+....................+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-----
[root@k-m-1 ssl]# ls
tls.crt  tls.key

# 创建Secret来管理它
[root@k-m-1 ssl]# kubectl create secret tls nginx --cert=tls.crt --key tls.key 
secret/nginx created

# 创建ApisixTls来引用这个Secret
# apisix-tls.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixTls
metadata:
  name: nginx
spec:
  hosts:
  - nginx.kudevops.cn
  secret:
    name: nginx
    namespace: default
[root@k-m-1 ~]# kubectl apply -f apisix-tls.yaml 
apisixtls.apisix.apache.org/nginx created

# 我们去配置一下强制跳转和TLS
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: nginx
spec:
  http:
  - name: root
    match:
      hosts:
      - nginx.kudevops.cn
      paths:
      - "/*"
    plugins:
    - name: proxy-rewrite
      enable: true
      config:
        regex_uri: ["^/nginx(/|$)(.*)", "/$2"]
    - name: redirect
      enable: true
      config:
        http_to_https: true
    backends:
    - serviceName: nginx
      servicePort: 80

image

可以看到我的请求被强转了,而且是HTTPS,但是一定要看插件说明,部分功能是不能共用的,其实总结下来我们用APISIX 就是为了它的插件,所以大家需要去了解一下它的插件的作用,这个是非常有必要的。

8:APISIX认证

身份认证在日常是非常常见的功能,大家平时基本都可以接触到,比如用支付宝,微信等消费的时候密码,人脸等公司上下班打卡以及网站/APP登录账号密码登录等,其实都是身份认证的体现。

基础的认证插件比如:Key-Auth,Basic-Auth,它们是通过账号密码的方式进行认证,复杂一些的认证插件比如:Hmac-Auth,JWT-Auth,如Hmac-Auth通过请求信息做一些加密,生成一个签名,当API调用的方将这个签名携带到Apache APISIX,Apache APISIX会以相同的方式计算签名,只有签名方和应用调用方认证相同才予以通过,其他则是一些通过认证协议和联合第三方组件进行合作的认证协议,例如:OpenID-Connect身份认证机制,以及LDAP认证等。

Apache APISIX还可以针对每个Consumer(即调用方应用)去做不同级别的插件配置,比如我们创建两个消费者Consumer A和Consumer B,我们将Consumer A应用到应用1,则后续应用1的访问将开启Consumer A这部分插件,例如IP黑白名单,限制并发数,将Consumer B应用给应用2,也就意味着应用2开启了Consumer B的所有插件,说白了它就是一个插件的组。

8.1:basic-auth

首先我们来了解一下基本的APISIX认证是如何使用的,basic-auth是一个认证插件,它需要与Consumer一起配合才能工作,添加Basic Auth到一个Service或者Route,然后Consumer将其用户名和密码添加到请求头中,以认证其请求。

首先我们要在APISIX Consumer消费者中添加basic auth认证配置,为其指定用户名密码,我们这里在APISIX Ingress中,可以通过ApisixConsumer资源对象进行配置,比如这里我们为前面的nginx应用添加一个基本的认证
# apisix-basic-auth.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
  name: nginx-basic
spec:
  authParameter:
    basicAuth:
      value:
        username: admin
        password: admin
ApisixConsumer资源对象只需要配置authParameter认证参数即可,目前支持basicAuth,hmacAuth,jwtAuth,keyAuth,wolfRBAC等多种认证类型,在basicAuth下面可以通过value可直接去配置相关的username和password也可以直接使用Secret资源对象进行配置,比起明文可能更安全点。

然后我们在ApisixRoute中添加authentication,将其开启并指定认证类型,就可以实现Consumer去完成相关配置认证了
[root@k-m-1 ~]# kubectl apply -f apisix-basic-auth.yaml 
apisixconsumer.apisix.apache.org/nginx-basic created

# 然后我们将这个认证在我们的ApisixRoute去开启
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: nginx
spec:
  http:
  - name: root
    match:
      hosts:
      - nginx.kudevops.cn
      paths:
      - "/*"
    plugins:
    - name: proxy-rewrite
      enable: true
      config:
        regex_uri: ["^/nginx(/|$)(.*)", "/$2"]
    backends:
    - serviceName: nginx
      servicePort: 80
    authentication:
      enable: true
      type: basicAuth
[root@k-m-1 ~]# kubectl get apisixconsumers
NAME          AGE
nginx-basic   5h55m
[root@k-m-1 ~]# kubectl apply -f apisix-rewrite.yaml 
deployment.apps/nginx unchanged
service/nginx unchanged
apisixroute.apisix.apache.org/nginx configured

# 访问Nginx然后可以看到的确是要认证的,我们需要输入账号密码然后才可以返回信息,我们输入一下

image

image

8.2:consumer-restrication

其实这个时候我们可能会有一个疑问,这个路由里面貌似也没有指定某个Consumer,那么如果需要其他的Consumer去认证怎么办,并且如果我有多个Basic Auth认证的情况下那岂不是都会生效了,其实却是是这样的,这就是Apisix的机制,但是如果说想让Consumer A只生效了Route A上Consumer B只生效在Route B上面的话,要实现这个功能就需要应用另一个插件:consumer-restication。

consumer-restrication插件可以根据选择不同对象做相应的访问限制,该插件可配置的属性也是挺多的,我们来看一下
名称 类型 必选项 默认值 有效值 描述
type string consumer_name ["consumer_name", "consumer_group_id", "service_id", "route_id"] 支持设置访问限制的对象类型。
whitelist array[string] 加入白名单的对象,优先级高于 allowed_by_methods
blacklist array[string] 加入黑名单的对象,优先级高于 whitelist
rejected_code integer 403 [200,...] 当请求被拒绝时,返回的 HTTP 状态码。
rejected_msg string 当请求被拒绝时,返回的错误信息。
allowed_by_methods array[object] ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE", "PURGE"] 为 Consumer 设置的允许的 HTTP 方法列表。
其中的Type字段是个枚举类型,可以设置如下值:
1:consumer_name:把Consumer的username列入白名单或者黑名单来限制Consumer对Route或Service的访问
2:consumer_group_id:把Consumer Group的id列入白名单或者黑名单来限制Consumer对Route或Service的访问
3:service_id:把Service的id列入白名单或者黑名单来限制Consumer对Service的访问,需要结合授权插件一起使用
4:route_id:把Route的id列入白名单或黑名单来限制Consumer对Route的访问

具体的使用大家可以自己结合插件的使用前面插件的使用方式去官网发现以下如何使用,这里就不多赘述了

8.3:jwt-auth

在平时我们用的最多的也就是这个JWT的认证了,同样在APISIX中也有支持jwt-auth的插件,它同样也是需要与Consumer结合才可以使用,我们只需要添加JWT Auth到一个Service或者Route,让后Consumer将其密钥添加到查询字符串参数,请求头或者Cookie中以验证其请求即可

当然除了通过ApisixConsumer这个CRD去配置之外,我们也可以直接通过Dashboard去配置它,那么我们来看看如何配置

image
image
image
image
image
image
image
image

创建完成之后我们需要使用它,那么我们来看看在nginx的应用里面如何使用它
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: nginx
spec:
  http:
  - name: root
    match:
      hosts:
      - nginx.kudevops.cn
      paths:
      - "/*"
    plugins:
    - name: proxy-rewrite
      enable: true
      config:
        regex_uri: ["^/nginx(/|$)(.*)", "/$2"]
    backends:
    - serviceName: nginx
      servicePort: 80
    authentication:
      enable: true
      type: jwtAuth
[root@k-m-1 ~]# kubectl apply -f apisix-rewrite.yaml 
deployment.apps/nginx unchanged
service/nginx unchanged
apisixroute.apisix.apache.org/nginx configured

# 验证JWT
[root@k-m-1 ~]# curl -i http://nginx.kudevops.cn:32150/
HTTP/1.1 401 Unauthorized
Date: Sun, 14 May 2023 19:11:11 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.3.0

{"message":"Missing JWT token in request"}

# 可以看到这里已经提示我们没有Token了,那么我们如何生成这个Token呢?我们用Helm安装APISIX的时候没有开启一个插件叫做public-api这个插件所以我们需要去helm的values内加一下这个插件
apisix:
  enabled: true
gateway:
  type: NodePort
  http:
    enabled: true
    servicePort: 80
    containerPort: 9080
  tls:
    enabled: true  # 启用TLS
    servicePort: 443
    containerPort: 9443
etcd:
  enabled: true
  replicaCount: 1
  persistence:
    enabled: true
    storageClass: managed-nfs-storage
dashboard:
  enabled: true
  config:
    conf:
      etcd:
        endpoints:
        - apisix-etcd:2379
        prefix: "/apisix"
        username: admin
        password: admin
ingress-controller:
  enabled: true
  config:
    apisix:
      serviceName: apisix-admin
      serviceNamespace: apisix
# 3.3.0版本我测试的时候,如果开启Plugin的话需要手动添加自己所需要的插件,否则会不生效的。
plugins:
- jwt-auth
- public-api   # 我们就是为了开启它
[root@k-m-1 ~]# helm upgrade --install apisix ./apisix -f ./apisix/apisix-values.yaml -n apisix --create-namespace 
Release "apisix" has been upgraded. Happy Helming!
NAME: apisix
LAST DEPLOYED: Mon May 15 03:17:14 2023
NAMESPACE: apisix
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace apisix -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway)
  export NODE_IP=$(kubectl get nodes --namespace apisix -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
  
[root@k-m-1 ~]# kubectl port-forward --address 0.0.0.0 -n apisix svc/apisix-gateway 80:80 443:443
Forwarding from 0.0.0.0:80 -> 9080
Forwarding from 0.0.0.0:443 -> 9443


curl http://10.96.2.97:9180/apisix/admin/routes/jas \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/apisix/plugin/jwt/sign",
    "plugins": {
        "public-api": {}
    }
}'

{"key":"/apisix/routes/jas","value":{"id":"jas","status":1,"plugins":{"public-api":{}},"update_time":1684097874,"uri":"/apisix/plugin/jwt/sign","create_time":1684097874,"priority":0}}


curl http://10.96.2.97:9180/apisix/admin/consumers \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "username": "default_jwt_auth",
    "plugins": {
        "jwt-auth": {
            "key": "user-key",
            "secret": "my-secret-key"
        }
    }
}'

{"key":"/apisix/consumers/default_jwt_auth","value":{"plugins":{"jwt-auth":{"key":"user-key","base64_secret":false,"lifetime_grace_period":0,"secret":"my-secret-key","exp":86400,"algorithm":"HS256"}},"username":"default_jwt_auth","create_time":1684098244,"update_time":1684098244}}


curl http://10.96.2.97:9180/apisix/admin/routes/jas \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/apisix/plugin/jwt/sign",
    "plugins": {
        "public-api": {}
    }
}'

{"key":"/apisix/routes/jas","value":{"plugins":{"public-api":{}},"create_time":1684098304,"priority":0,"uri":"/apisix/plugin/jwt/sign","update_time":1684098304,"id":"jas","status":1}}

# 请求获得Token
[root@k-m-1 ~]# curl http://127.0.0.1/apisix/plugin/jwt/sign?key=user-key
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY4NDE4NDc2MH0.JdHCDT7Pwg8mWCHhIUKAY8IDxEvp1wp-GbeD_U1h3lQ

# 使用Token请求Nginx服务
[root@k-m-1 ~]# curl http://nginx.kudevops.cn:32150 -I
HTTP/1.1 401 Unauthorized
Date: Sun, 14 May 2023 21:09:59 GMT
Content-Type: text/plain; charset=utf-8
Connection: keep-alive
Server: APISIX/3.3.0

# 请求头
[root@k-m-1 ~]# curl http://nginx.kudevops.cn:32150 -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY4NDE4NDc2MH0.JdHCDT7Pwg8mWCHhIUKAY8IDxEvp1wp-GbeD_U1h3lQ' -I
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 615
Connection: keep-alive
Date: Sun, 14 May 2023 21:10:00 GMT
Last-Modified: Tue, 28 Mar 2023 15:01:54 GMT
ETag: "64230162-267"
Accept-Ranges: bytes
Server: APISIX/3.3.0

# 带参
[root@k-m-1 ~]# curl http://nginx.kudevops.cn:32150/?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY4NDE4NDc2MH0.JdHCDT7Pwg8mWCHhIUKAY8IDxEvp1wp-GbeD_U1h3lQ -I
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 615
Connection: keep-alive
Date: Sun, 14 May 2023 21:14:01 GMT
Last-Modified: Tue, 28 Mar 2023 15:01:54 GMT
ETag: "64230162-267"
Accept-Ranges: bytes
Server: APISIX/3.3.0

# 带Cookie
[root@k-m-1 ~]# curl http://nginx.kudevops.cn:32150 --cookie jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY4NDE4NDc2MH0.JdHCDT7Pwg8mWCHhIUKAY8IDxEvp1wp-GbeD_U1h3lQ -I
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 615
Connection: keep-alive
Date: Sun, 14 May 2023 21:14:52 GMT
Last-Modified: Tue, 28 Mar 2023 15:01:54 GMT
ETag: "64230162-267"
Accept-Ranges: bytes
Server: APISIX/3.3.0

# 这样我们的JWT也就搞好了,业就意味着我们的JWT认证做好了
posted @ 2023-05-15 06:37  Layzer  阅读(1314)  评论(0编辑  收藏  举报