Apache APISIX を触ってみる

## この記事の目的

以下の記事で Kong Gateway の公式ドキュメントを読んで動作確認を行いました．

https://qiita.com/caunu-s/items/636aff1d04778fbbd779

最近人気が伸びつつある API ゲートウェイである Apache APISIX についても同様に[公式ドキュメント](https://apisix.apache.org/)に沿って動作確認を行ったので，こちらの記事に残します．

:::note info
こちらの記事を参考にして Apaxhe APISIX の API Gateway, Dashboard, Ingress Controller 環境を構築することができます．
:::

## デモ構成

![kind-architecture.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/fda278e0-7071-f999-a471-03d29e168f04.png)

## そもそも API ゲートウェイとは何ぞや

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/77e8edc2-7df6-5b7d-8617-9becd5740754.png)

技術の進歩が速い中で正確に「これが定義である」というのは難しいですが，CNCF Landscape の API ゲートウェイの章[^CNCF/LANDSCAPE-API-GATEWAY]では

[^CNCF/LANDSCAPE-API-GATEWAY]: https://cncf.landscape2.io/guide#orchestration-management--api-gateway

- アプリケーション間のリクエストや通信を一元管理するもの
- ユーザーがやろうとしていることができるかを示すインターフェース
- ユーザーがやったことを評価・記録するもの

のように説明されています．
以上だけだと "高級なプロキシ" のような感じもしてしまいますが，Gartner が出している Magic Quadrant for API Management (11 Oct. 2023) [^GARTNER/MAGIC-QUADRA-API] を確認してみると "API マネジメント" の分野で必須・標準とされる機能には以下があります．

[^GARTNER/MAGIC-QUADRA-API]: https://www.gartner.com/doc/reprints?id=1-2F7MCTJF&ct=231004&st=sb

- 開発者ポータル (管理 GUI)
- ランタイム管理，監視などの運用・制御機能
- ポリシー管理などのセキュリティ機能
- API 設計・開発の補助機能

これらを持ち合わせており，かつ

- 追加機能がどれくらいあるか (どこまで無償で使えるか)
- 運用が楽な設計になっているか
- パフォーマンス性が良いか
- 強力な開発体制が整えられて更新が続けられているか
- 拡張性が良く，機能拡張方法が組織で運用可能なレベルとマッチしているか
- ローカライズされているか (日本語でのサポートなど)

などが差別化要素となっているものが API ゲートウェイ製品として現れてきているように思います．

今日では様々な API ゲートウェイ製品が見つかるので，少しずつ試して調査しながら良さげなものを見つけていくのが良いと考えています．

Kong Gateway や Apache APISIX はその中の一つという位置付けです．

## Apache APISIX のサービス紹介

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/26491f90-c540-92eb-9c27-d4d84d3980dc.png)

> Apache APISIX provides rich traffic management features like Load Balancing, Dynamic Upstream, Canary Release, Circuit Breaking, Authentication, Observability, etc.[^APISIX]

[^APISIX]: https://apisix.apache.org/

Apache APISIX は様々な機能を複数のサービスに分けて提供していますので，順にご紹介します．

| Service | Summary |
|----|----|
| API Gateway | A dynamic, real-time, high-performance API gateway. |
| Dashboard | Designed to make it as easy as possible for users to operate Apache APISIX through a frontend interface. |
| Ingress Controller | APISIX Ingress Controller for Kubernetes. |

### [API Gateway](https://github.com/apache/apisix)

> APISIX API Gateway provides rich traffic management features such as load balancing, dynamic upstream, canary release, circuit breaking, authentication, observability, and more.

Apache APISIX API Gateway はクラウドネイティブを強く意識した OSS の API ゲートウェイ製品を提供します．
マルチプロトコルやセキュリティ機能など豊富な機能が提供されており，全て無償で利用することができます．

これらの機能はプラグインとして提供されており，以下の Plugin Hub から利用方法を参照することができます．

https://apisix.apache.org/plugins/

[公式のアーキテクチャ紹介](https://apisix.apache.org/docs/apisix/architecture-design/apisix/)によると，APISIX は [Nginx](https://github.com/nginx) 及び [OpenResty](https://github.com/openresty) の [lua-nginx-module](https://github.com/openresty/lua-nginx-module) の上に構築されています．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/eedfd709-b40d-f144-5b49-6d62c646a9c7.png)

これは Kong Gateway と同じであり，Nginx/Lua の中身の C と Go によるパフォーマンスの比較を行うよりも，Just-In-Time (JIT) を信用して Lua で発展したルーティング機能の柔軟性を活用するように舵を切った結果とのことです．
特に高パフォーマンスが求められる Core 部分では C での書き直しも行っているようです．

https://apisix.apache.org/blog/2021/08/25/why-apache-apisix-chose-nginx-and-lua/

一方で利用しているデータベースには明確に差があり，Kong Gateway は PostgreSQL を利用していたのに対して APISIX では [etcd](https://etcd.io/) を利用しています．
これは他のデータベースと比較して etcd がスケールを行いやすく，単一障害点の排除や同時実行制御に強みを持たせるためであると説明されています．[^APISIX/DIFFER]

[^APISIX/DIFFER]: https://apisix.apache.org/docs/apisix/FAQ/#why-does-apache-apisix-use-etcd-for-the-configuration-center

また，Kong Gateway と同様に Admin API を提供しており，管理者による設定とゲートウェイのアクセスに利用するポートが分けられています．

### [Dashboard](https://github.com/apache/apisix-dashboard)

APISIX における管理 GUI (開発者ポータル) を担うサービスです．
各種ルーティングの設定やプラグインの有効化などは，Admin API を利用するか，Dashboard を利用して行うことになります．

現在では APISIX が提供する全ての機能が Dashboard から管理できるようにはなっておらず，順に追加している途中であるとのことです．

なお，利用を開始するには API Gateway とは別にデプロイしてあげる必要があります．
言語は英語とトルコ語，中国語をサポートしています．

特に GUI が気になる場合は [Online Playground](https://apisix-dashboard.apiseven.com/) から気軽に雰囲気を確かめることができます(ユーザー名とパスワードは ```admin```/```admin``` です)．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/18793482-b5e5-d27e-ad12-742d68c2aa43.png)

触ってみた感想としては，

- 使いやすさは Kong Gateway (Free) と同程度
  - ボタンの位置など探すのにすごく苦労するわけではないが，多少は慣れが必要
  - 設定値を json として出力・確認するのが容易
- 良くも悪くも Kong Gateway の GUI よりもタイムアウトまでの時間が短く，再ログインを求められる頻度が高い．
- Grafana ダッシュボードとの連携を行うことができるのは良さそう

といったところでした．

### [Ingress Controller](https://github.com/apache/apisix-ingress-controller)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/36529123-c079-9fb9-9d44-3d2499132c06.png)

Ingress Controller は APISIX スタックの中でコントロールプレーンにあり，Kubernetes カスタムリソース定義 (CRD) に基づいた独自の実装を可能にします．
これによってルーティング設定などを実装することができます．

CRD については以下で詳細な説明がされています．

https://qiita.com/cvusk/items/773e222e0971a5391a51

本記事ではあまり触れませんが，[Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way) をもじった [APISIX Ingress Controller the Hard Way](https://apisix.apache.org/docs/ingress-controller/tutorials/the-hard-way/) があるので別の機会に読み進めて試してみたいと思います．

## 動作確認環境

動作確認を行った環境情報を以下に示します．

| Name | Version |
|----|----|
| Windows 11 | 22H2 |
| Ubuntu (WSL2) | 20.04.6 LTS |
| Docker | 20.10.17 |
| Docker Compose | v2.10.2 |
| Kubernetes | v1.27.3 |
| kubectl client | v1.25.0 |
| kubectl kustomize | v4.5.7 |
| KinD | v0.20.0 |
| helm | v3.13.3 |
| Go | v1.21.3 |
| APISIX API Gateway | 3.8.0-debian |
| APISIX Dashboard | 3.0.0-alpine |
| APISIX Ingress Controller | 1.7.1 |
| bitnami/etcd | 3.5.7-debian-11-r14 |
| Prometheus | v2.48.1 |

## Kong Gateway との比較

個人的に Kong Gateway と Apache APISIX と比較した結果を先に記述しておきます．

### Kong Gateway の方が優れていると思われる点

- 先発として知名度や人気が高い．
- デジタル庁からも推奨 API ゲートウェイとされている．[^PUBLICKEY/KONG]
- Apache APISIX のように Kubernetes の習熟までは必要なく，Docker のみ使えれば構築に支障はない．
- カスタムプラグイン作成も比較的容易．
- 有償プランによるオフィシャルなサポート(Konnect) もあるので，いざとなったら課金で様々な問題を解決できる可能性がある．

[^PUBLICKEY/KONG]: https://www.publickey1.jp/blog/22/apikong_gatewaypr.html

### Apache APISIX の方が優れていると思われる点

- クラウドネイティブ性が非常に高く，(その気になれば) 保守性の高いサービスを構築できる．
- 独自の Ingress Controler などの Kubernetes 環境に即した構築手法も提供している．
- Kong Gateway のよりもパフォーマンス・性能が良い (らしい)．[^APISEVEN/VSKONG]
- etcd を利用することでアーキテクチャの肥大化を防ぎ，同時実行制御に関して処理性能が特に高い．
- Java など Kong Gateway にはなかった他言語でのプラグイン開発がサポートされている．
- すさまじい速度で Contributor を増やしている．
- 一般提供のプラグインの種類も豊富 (Keycloak 認可プラグインや Elasticsearch ロガープラグインなどもサポート)
- 完全無償 (Apache)

[^APISEVEN/VSKONG]: https://api7.ai/apisix-vs-kong

## API Gateway の起動

APISIX を起動する方法として，公式から

- Docker ([Docs](https://apisix.apache.org/docs/docker/build/), [GitHub](https://github.com/apache/apisix-docker/tree/master))
- Helm Charts ([Docs](https://apisix.apache.org/docs/helm-chart/apisix/), [GitHub](https://github.com/apache/apisix-helm-chart))

が提供されています．
ただし Docker イメージは Apache Software Foundation (ASF) として公式にリリースしているわけではなく，各人が ```Dockerfile``` からビルドして利用することが推奨されています．

APISIX が売りとしている "Fully-Dynamic" (設定や動作の変更時に再起動が不要である) を保証する上でも Kubernetes がマッチしているように感じます．

上記より Kubernetes 環境があれば Helm Charts を使うのが良いと思います．
今回はローカルで [KinD](https://kind.sigs.k8s.io/) のシングルノード構成で一連の環境を立ち上げていきます．
(Minikube では etcd の起動に失敗することまで確認しましたが，原因調査はしていません)

```shell
# helm レポジトリ追加，更新，デプロイ
helm repo add apisix https://charts.apiseven.com && \
helm repo update && \
helm upgrade --install apisix apisix/apisix --create-namespace --namespace apisix \
--set dashboard.enabled=true --set ingress-controller.enabled=true \
--set ingress-controller.config.apisix.serviceNamespace=apisix \
--set prometheus.enabled=true --set prometheus.serviceMonitor.enabled=true \
--set prometheus.serviceMonitor.namespace=apisix \
--set prometheus.serviceMonitor.selector.release=apisix
> Release "apisix" does not exist. Installing it now.
> NAME: apisix
> LAST DEPLOYED: Thu Jan 25 12:42:29 2024
> NAMESPACE: apisix
> STATUS: deployed
> REVISION: 1
> 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

# 確認
kubectl -n apisix get pods
> NAME                                         READY   STATUS    RESTARTS      AGE
> apisix-78975cc787-z8bsg                      1/1     Running   0             2m35s
> apisix-dashboard-b58db9d6-bxzqb              1/1     Running   4 (77s ago)   2m35s
> apisix-etcd-0                                1/1     Running   0             2m35s
> apisix-etcd-1                                1/1     Running   0             2m35s
> apisix-etcd-2                                1/1     Running   0             2m35s
> apisix-ingress-controller-7589b94554-dgbd6   1/1     Running   0             2m35s

kubectl -n apisix get services
> NAME                                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
> apisix-admin                               ClusterIP   10.96.192.241   <none>        9180/TCP                     3m15s
> apisix-dashboard                           ClusterIP   10.96.8.1       <none>        80/TCP                       3m15s
> apisix-etcd                                ClusterIP   10.96.43.152    <none>        2379/TCP,2380/TCP            3m15s
> apisix-etcd-headless                       ClusterIP   None            <none>        2379/TCP,2380/TCP            3m15s
> apisix-gateway                             NodePort    10.96.213.35    <none>        80:31167/TCP                 3m15s
> apisix-ingress-controller                  ClusterIP   10.96.166.85    <none>        80/TCP                       3m15s
> apisix-ingress-controller-apisix-gateway   NodePort    10.96.166.137   <none>        80:32409/TCP,443:32403/TCP   3m15s

# ポートフォワード http://localhost:8080 で API Gateway にアクセスできるようになる
kubectl -n apisix port-forward svc/apisix-gateway 8080:80
# ポートフォワード http://localhost:9080 で APISIX Dashboard にアクセスできるようになる
kubectl -n apisix port-forward svc/apisix-dashboard 9080:80
# ポートフォワード http://localhost:9180 で APISIX Admin API にアクセスできるようになる
kubectl -n apisix port-forward svc/apisix-admin 9180:9180
```

APISIX Admin API のデフォルト API Key は ```edd1c9f034335f136f87ad84b625c8f1``` です (詳しくは設定ファイル ```config.yaml``` の設定値を定義している [```values.yaml```](https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix/values.yaml#L330) をご覧ください)．

APISIX Dashboard のデフォルトのログインユーザ/パスワードは ```admin```/```admin``` です (詳しくは設定ファイル ```config.yaml``` の設定値を定義している [```values.yaml```](https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix-dashboard/values.yaml#L127) をご覧ください)．

http://localhost:9080/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/847d7ca9-e3e4-187c-61de-4b26cf258d84.png)

APISIX API Gateway にも利用ユーザを定義する Consumer があります．

Consumer を作成してプラグインを設定していくことで，API を利用するユーザごとに挙動を変えることができます．
なお，APISIX でも Consumer は横断的に作成されます．

/consumers エンドポイントに POST をすることで Consumer を登録できます．

```shell
curl -i "http://localhost:9180/apisix/admin/consumers?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "username": "foo"
}'
```

http://localhost:9080/consumer/list
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/3ed2523e-7bc0-1c3d-4e9a-8a53162812cb.png)

## 任意のリクエストをプロキシ

![proxy.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/296b1eeb-4175-05ec-5604-2ac40f389651.png)

[Kong Gateway と同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#%E4%BB%BB%E6%84%8F%E3%81%AE%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7)で Route と Upstream の概念を使ってルーティング&プロキシを行います．
ただし，Kong Gateway では Upstream API と Route API を順番に叩いて URI と宛先 API を設定していたのに対し，APISIX では Route API ですべて同時に設定することができます．

外部の API にルーティングを行う際は API Gateway から疎通可能であることを十分に確認してください．

ここでは同じ namespace に立てられた API サーバーに対するルーティングを試します．
まず適当なレスポンスを返すサーバーを API Gateway と同じ namespace ```apisix``` にたてます．
Golang で ```Hello, APISIX!``` と返す API サーバーを作成してコンテナ化し，```kubectl apply``` していきます．

```golang: main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, APISIX!")
	})

	http.ListenAndServe(":10080", nil)
}
```

```Dockerfile: Dockerfile
FROM golang:1.21.3 as builder

WORKDIR /app
COPY main.go .

RUN go mod init helloapp
RUN CGO_ENABLED=0 GOOS=linux go build -o helloapp

FROM alpine:3.19.0

WORKDIR /app
COPY --from=builder /app/helloapp /app/

EXPOSE 10080
CMD ["/app/helloapp"]
```

```yaml: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloapp
  labels:
    app: helloapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloapp
  template:
    metadata:
      labels:
        app: helloapp
    spec:
      containers:
      - name: helloapp
        image: caunus/helloapp:latest
        ports:
        - containerPort: 10080
```

```yaml: service.yaml
apiVersion: v1
kind: Service
metadata:
  name: helloapp
spec:
  selector:
    app: helloapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 10080
  type: LoadBalancer
```

```shell
# コンテナを作成して DockerHub にあげておく
docker build -t caunus/helloapp:latest .
docker push caunus/helloapp:latest

# apisix namespace に kubectl apply
kubectl -n apisix apply -f deployment.yaml
> deployment.apps/helloapp created
kubectl -n apisix apply -f service.yaml
> service/helloapp created

# 確認
kubectl -n apisix get pods -o wide
> NAME                                         READY   STATUS    RESTARTS        AGE     IP            NODE                 NOMINATED NODE   READINESS GATES
> ...
> helloapp-98d8d47f-m6gss                      1/1     Running   0               19s     10.244.0.14   kind-control-plane   <none>           <none>

kubectl -n apisix get services
> NAME                                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
> ...
> helloapp                                   LoadBalancer   10.96.35.128    <pending>     80:31910/TCP                 2m6s
```

以上で同じ namespace ```apisix``` にデプロイされている Pod からは http://helloapp で helloapp Pod からの応答を取得することができます．
気になる方は helloapp Pod に入って curl を取得し叩いてみることで確認できます．

```shell
kubectl exec -n apisix -it helloapp-98d8d47f-m6gss -- /bin/sh
> apk add curl
> curl http://helloapp
>> Hello, APISIX!
```

下準備ができたのでルーティング設定をしてから API Gateway にアクセスしてみます．

```shell
# Route を作成
curl -i "http://localhost:9180/apisix/admin/routes?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "id": "example_route",
  "uri": "/mock",
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "helloapp:80": 1
    }
  }
}'

# API Gateway に問い合わせ
curl -X GET http://localhost:8080/mock
> Hello, APISIX!
```

無事ルーティングできていることが確認できました．
Dashboard を確認すると，先ほど作成した Route が登録されていることがわかります．
ただし Upstream としては登録されないことが管理面で少し気になりました．

http://localhost:9080/routes/list
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/5137731a-0e90-61a6-aecc-70db1b4ab918.png)

http://localhost:9080/upstream/list
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/37f2a8d3-09ee-92b0-1202-1e867d1b42e3.png)

そこで Dashboard で先ほど作成した Route は削除しておき，改めて Upstream と Route を分けて設定してみます．

```shell
# Upstream を作成
curl -i "http://localhost:9180/apisix/admin/upstreams?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "id": "example_upstream",
  "type": "roundrobin",
  "nodes": [
    {
      "host": "helloapp",
      "port": 80,
      "weight": 1
    }
  ]
}'

# Route を作成
curl -i "http://localhost:9180/apisix/admin/routes?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "id": "example_route",
  "uri": "/mock",
  "upstream_id": "example_upstream"
}'

# API Gateway に問い合わせ
curl -X GET http://localhost:8080/mock
> Hello, APISIX!
```

上記で Upstream と Route を分けて管理をできることがわかりました．

http://localhost:9080/routes/list
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/2d5593b8-d01f-54ce-f799-7a3c00996fad.png)

http://localhost:9080/upstream/list
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b7c1b498-fb4b-1d83-1a47-d4b8881e10f1.png)

## ロードバランシング

![lb.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/e8e9b50a-a7d1-fd3b-1f0b-a40415f09aeb.png)

[Kong Gateway と同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#%E3%83%AD%E3%83%BC%E3%83%89%E3%83%90%E3%83%A9%E3%83%B3%E3%82%B7%E3%83%B3%E3%82%B0)に Upstream に宛先となる API を登録することで簡単にロードバランシングができます．

[任意のリクエストをプロキシ](#任意のリクエストをプロキシ) と同様に，Golang で ```Bonjour, APISIX!``` と返す API サーバーを作成してコンテナ化し，```kubectl apply``` していきます．
ラウンドロビンで ```Hello, APISIX!``` と ```Bonjour, APISIX!``` を得ることにします．

```golang: main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Bonjour, APISIX!")
	})

	http.ListenAndServe(":11080", nil)
}
```

```Dockerfile: Dockerfile
FROM golang:1.21.3 as builder

WORKDIR /app
COPY main.go .

RUN go mod init bonjourapp
RUN CGO_ENABLED=0 GOOS=linux go build -o bonjourapp

FROM alpine:3.19.0

WORKDIR /app
COPY --from=builder /app/bonjourapp /app/

EXPOSE 11080
CMD ["/app/bonjourapp"]
```

```yaml: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bonjourapp
  labels:
    app: bonjourapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bonjourapp
  template:
    metadata:
      labels:
        app: bonjourapp
    spec:
      containers:
      - name: bonjourapp
        image: caunus/bonjourapp:latest
        ports:
        - containerPort: 11080
```

```yaml: service.yaml
apiVersion: v1
kind: Service
metadata:
  name: bonjourapp
spec:
  selector:
    app: bonjourapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 11080
  type: LoadBalancer
```

```shell
# コンテナを作成して DockerHub にあげておく
docker build -t caunus/bonjourapp:latest .
docker push caunus/bonjourapp:latest

# apisix namespace に kubectl apply
kubectl -n apisix apply -f deployment.yaml
> deployment.apps/bonjourapp created
kubectl -n apisix apply -f service.yaml
> service/bonjourapp created

# 確認
kubectl -n apisix get pods -o wide
> NAME                                         READY   STATUS    RESTARTS      AGE     IP            NODE                 NOMINATED NODE   READINESS GATES
> ...
> bonjourapp-bd6845f99-w68v9                   1/1     Running   0             19s     10.244.0.15   kind-control-plane   <none>           <none>
> helloapp-98d8d47f-m6gss                      1/1     Running   0             6m26s   10.244.0.14   kind-control-plane   <none>           <none>

kubectl -n apisix get services
> NAME                                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
> ...
> bonjourapp                                 LoadBalancer   10.96.128.240   <pending>     80:32252/TCP                 84s
> helloapp                                   LoadBalancer   10.96.35.128    <pending>     80:31910/TCP                 7m27s
```

下準備ができたのでルーティング設定をしてから API Gateway にアクセスしてみます．

```shell
# Upstream を更新
curl -i "http://localhost:9180/apisix/admin/upstreams?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "id": "example_upstream",
  "type": "roundrobin",
  "nodes": [
    {
      "host": "helloapp",
      "port": 80,
      "weight": 1
    },
    {
      "host": "bonjourapp",
      "port": 80,
      "weight": 1
    }
  ]
}'

# API Gateway に問い合わせ
curl -X GET http://localhost:8080/mock
> Hello, APISIX!
curl -X GET http://localhost:8080/mock
> Bonjour, APISIX!
curl -X GET http://localhost:8080/mock
> Bonjour, APISIX!
curl -X GET http://localhost:8080/mock
> Hello, APISIX!
```

以上でラウンドロビンでロードバランシングができていることが確認できました．

## リクエストの流量制御

[Kong Gateway と同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AE%E6%B5%81%E9%87%8F%E5%88%B6%E5%BE%A1)に [limit-count プラグイン](https://apisix.apache.org/docs/apisix/plugins/limit-count/)や [limit-req プラグイン](https://apisix.apache.org/docs/apisix/plugins/limit-req/)などを利用してリクエストの流量制御を行うことができます．

プラグインはグローバルで有効化することも Route 単位で有効化することもできます．
プラグインを無効化するには ```"_meta": {"disable": true}``` で再登録します．よって一度設定した情報は残ることになります．

```shell
# 特定の Route で limit-count プラグインを有効化
curl -i "http://127.0.0.1:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "plugins": {
    "limit-count": {
        "count": 2,
        "time_window": 10,
        "rejected_code": 503
     }
  }
}'

# 3 回連続でリクエストを送ると 503 が返る
curl -X GET http://localhost:8080/mock
> Bonjour, APISIX!
curl -X GET http://localhost:8080/mock
> Bonjour, APISIX!
curl -X GET http://localhost:8080/mock
> <html>
> <head><title>503 Service Temporarily Unavailable</title></head>
> <body>
> <center><h1>503 Service Temporarily Unavailable</h1></center>
> <hr><center>openresty</center>
> <p><em>Powered by <a href="https://apisix.apache.org/">APISIX</a>.</em></p></body>
> </html>

# プラグインの無効化
curl -i "http://127.0.0.1:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "plugins": {
    "limit-count": {
        "_meta": {
                "disable": true
        }
     }
  }
}'
```

## レスポンスのキャッシュ

[Kong Gateway と同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5)に [proxy-cache プラグイン](https://apisix.apache.org/docs/apisix/plugins/proxy-cache/) を有効化することでレスポンスをキャッシュして，繰り返しのリクエストへの応答の高速化を図ることができます．
キャッシュ方法は disk と memory から選ぶことができます．
Kong Gateway では memory が基本だったと思うので差分と呼べるかもしれません．

プラグインはグローバルで有効化することも Route 単位で有効化することもできます．
プラグインを無効化するには ```"_meta": {"disable": true}``` で再登録します．よって一度設定した情報は残ることになります．

```shell
# 特定の Route で disk-based な proxy-cache プラグインを有効化
curl -i "http://localhost:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "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"]
        }
  }
}'

# プラグインの無効化
curl -i "http://localhost:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "plugins": {
    "proxy-cache": {
        "_meta": {
                "disable": true
        }
     }
  }
}'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/d283f6c3-7881-dcfc-18f5-8570e38dbf0d.png)

## API キー認証

[Kong Gateway と同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#api-%E3%82%AD%E3%83%BC%E8%AA%8D%E8%A8%BC)に [key-auth プラグイン](https://apisix.apache.org/docs/apisix/plugins/key-auth/) を有効化することで API キー認証を実装することができます．

いま起動時に作成した Consumer foo に key-auth プラグインを有効化する例を考えてみます．
ここで，[Consumer API](https://apisix.apache.org/docs/apisix/admin-api/#consumer-api) は PATCH をサポートしておらず追加・削除と参照しかできないため，改めて作成しなおす必要があります．
Kong Gateway では既存の Consumer にそのまま設定を追加できたので差分となります．

Consumer の設定が終わったら Route などでも key-auth プラグインを有効化することで認証が行われるようになります．

```shell
# 一旦 foo を削除
curl -i "http://localhost:9180/apisix/admin/consumers/foo?api_key=edd1c9f034335f136f87ad84b625c8f1" -X DELETE

# foo を再作成
curl -i "http://localhost:9180/apisix/admin/consumers?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "username": "foo",
  "plugins": {
    "key-auth": {
      "key": "password"
    }
  }
}'

# Route で有効化
curl -i "http://localhost:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "plugins": {
        "key-auth": {}
  }
}'

# リクエストを試す
curl -X GET http://localhost:8080/mock
> {"message":"Missing API key found in request"}
curl -X GET http://localhost:8080/mock -H 'apikey:password'
> Hello, APISIX!

# プラグインの無効化
curl -i "http://localhost:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
  "plugins": {
    "key-auth": {
        "_meta": {
                "disable": true
        }
     }
  }
}'
```

## Prometheus で監視

[Kong Gateway 同様](https://qiita.com/caunu-s/items/636aff1d04778fbbd779#prometheus-%E3%81%A7%E7%9B%A3%E8%A6%96)に Prometheus と連携して監視を行うことができます．
本記事では KinD 環境に Prometheus を起動して動作確認を行います．

ここで，API Gateway と Prometheus が接続できるように設定する必要があります．
一番簡単に接続できるようにする方法は ```apisix-gateway``` Pod にサイドカーとして Prometheus をたちあげることだと思いますが，Prometheus は API Gateway と合わせてスケーリングされても困るので今回は採用しません．
次点で APISIX と同じ namespace に Prometheus を起動することが考えられますが，一つの Prometheus で複数のサービスを管理したい立場の人 (特に金銭観点が多い気がしますが) も一定層いることを踏まえ，別の namespace で立ち上げる場合を考えたいと思います．


まずは Prometheus がスクレイピングを行うために API Gateway にポート開けを行います．
API Gateway 側ではデフォルトで ```9091``` のポートを利用します (詳しくは設定ファイル ```config.yaml``` の設定値を定義している [```values.yaml```](https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix/values.yaml#L575) をご覧ください)．

```service.yaml
apiVersion: v1
kind: Service
metadata:
  name: apisix-monitor
spec:
  selector:
    app.kubernetes.io/instance: apisix
    app.kubernetes.io/name: apisix
  ports:
  - name: apisix-monitor
    port: 9091
    protocol: TCP
    targetPort: 9091
  type: LoadBalancer
```

```shell
# Service 登録
kubectl -n apisix apply -f service.yaml
> service/apisix-monitor created

# 確認
kubectl -n apisix get services
> NAME                                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
> ...
> apisix-monitor                             LoadBalancer   10.96.68.125    <pending>     9091:32224/TCP               13s
> ...
```

こちらで ```9091``` ポートが利用できるようになったので，ポートフォワードして Prometheus 用の metrics が取得できることを確認します．

```shell
# ポートフォワード http://localhost:9091 で API Gateway の Prometheus 用 metrics を取得できるようになる
kubectl -n apisix port-forward svc/apisix-monitor 9091:9091

# metrics の取得確認
curl http://localhost:9091/apisix/prometheus/metrics
```

上記が確認できたら Prometheus を準備していきます．
ホストから ```localhost:9091``` で接続できるようにしたので Prometheus Pod からホストに接続できるように名前解決をできるようにする必要がありますが，ここでは [```host.docker.internal```](https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host) を利用します．

```yaml: configmap.yaml
apiVersion: v1
data:
  prometheus.yml: |
    scrape_configs:
      - job_name: 'apisix'
        scrape_interval: 15s
        metrics_path: "/apisix/prometheus/metrics"
        static_configs:
          - targets: ['host.docker.internal:9091']
kind: ConfigMap
metadata:
  name: prom-config
```

```yaml: deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  labels:
    app: prometheus
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.48.1
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
          securityContext:
            privileged: true
      volumes:
        - name: prometheus-config-volume
          configMap:
            name: prom-config
```

```yaml: service.yml
apiVersion: v1
kind: Service
metadata:
  name: prometheus
spec:
  selector:
    app: prometheus
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9090
  type: LoadBalancer
```

```shell
# Prometheus 起動
kubectl apply -f configmap.yaml
> configmap/prom-config created
kubectl apply -f deployment.yaml
> deployment.apps/prometheus created
kubectl apply -f service.yaml
> service/prometheus created

# 確認
kubectl get configmaps
> NAME               DATA   AGE
> ...
> prom-config        1      51s

kubectl get deployments
> NAME         READY   UP-TO-DATE   AVAILABLE   AGE
> prometheus   1/1     1            1           68s

kubectl get services
> NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
> ...
> prometheus   LoadBalancer   10.96.92.134   <pending>     80:30559/TCP   83s

# ポートフォワード http://localhost:9090 で Prometheus にアクセスできるようになる
kubectl port-forward svc/prometheus 9090:80
```

上記で Prometheus 起動が確認できたら Route に対してプラグインを有効化します．

```shell
# 特定の Route で Prometheus プラグインを有効化
curl -i "http://localhost:9180/apisix/admin/routes/example_route?api_key=edd1c9f034335f136f87ad84b625c8f1" -X PATCH -d '
{
    "plugins": {
        "prometheus":{}
    }
}'
```

http://localhost:9090/targets
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/baee7f27-f68f-9ace-c7bb-5802a1ecf71e.png)

http://localhost:9090/graph
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/ec5895eb-c3ae-73cd-7d58-ff74eabf8fc3.png)

```apisix_http_status``` の結果で ```/mock``` へのリクエストが 2 種類現れていますが，```kubectl -n apisix get services``` の結果と照らし合わせればこれらが helloapp と bonjourapp それぞれの Service の IP であることがわかり，現在の接続状況などの監視ができることがわかりました．

## カスタムプラグインでさらに拡張

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/7623c318-84c4-3c72-dd7c-bba4985a7b20.png)

[Kong Gateway 同様](https://qiita.com/caunu-s/items/b6ea0a86252ad7b3f6dc#kong-gateway-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%A8%E8%A8%80%E8%AA%9E%E9%81%B8%E6%8A%9E)にAPISIX でも Lua での外部プラグイン開発をサポートしています．[^APISIX/EXTERNAL-PLUGIN]

[^APISIX/EXTERNAL-PLUGIN]: https://apisix.apache.org/docs/apisix/external-plugin/

Lua で開発を行う場合は[公式ドキュメント](https://apisix.apache.org/docs/apisix/plugin-develop/)を参考にするとよいです．独自のコードは API Gateway がロードするように設定ファイルに記載を行います．
ここで，外部ライブラリを使う場合は Nginx の設定を書き換えて依存関係を登録したりと本体に手を加える必要がある可能性があります．

APISIX では Lua 以外の言語での拡張に関して，サイドカーとして起動するプラグイン用の Runner も準備しており RPC 呼び出しを活用してカスタムプラグインを実行します．

| Service | Summary |
|----|----|
| [Java Plugin Runner](https://github.com/apache/apisix-java-plugin-runner) | Runs Apache APISIX plugins written in Java. Implemented as a sidecar that accompanies Apache APISIX. |
| [Go Plugin Runner](https://github.com/apache/apisix-go-plugin-runner) | Runs Apache APISIX plugins written in Go. Implemented as a sidecar that accompanies Apache APISIX. |
| [Python Plugin Runner](https://github.com/apache/apisix-python-plugin-runner) | Runs Apache APISIX plugins written in Python. Implemented as a sidecar that accompanies Apache APISIX. |
| [Javascript Plugin Runner](https://github.com/zenozeng/apisix-javascript-plugin-runner) | Node.js / Deno Plugin Runner for APISIX (Alpha) |

また [```proxy-wasm``` SDK](https://github.com/proxy-wasm/spec) を利用した WebAssembly プラグインを差し込むこともできます．[^APISIX/WASM]

[^APISIX/WASM]: https://apisix.apache.org/docs/apisix/wasm/

こちらはまた別の記事で動作検証をしてみたいと思いますが，Kong Gateway よりかは複雑であることが伺えます．

## まとめ

Apache APISIX は日本語での紹介やサポートはほとんどなく，また Kubernetes への習熟が必須な API Gateway でしたが，その一方で多くの追加機能を提供していることがわかりました．

また強力な OSS の開発体制も整えられてきているように思います．

試用している中ではパフォーマンスの面で気になることは特にありませんでしたが，この記事を読んで興味が湧いた方は試してみていただいて，ご自身が求めるスペックが発揮できるかをご確認いただければと思います．

## 後片付け (オマケ)

お試し環境を削除する手順も念のため載せておきます．

```shell
# 後片付け
helm uninstall -n apisix apisix
kubectl delete namespace apisix
```