Customize APIs IX plug-ins using Python

Posted by rn14 on Fri, 11 Feb 2022 13:39:21 +0100

In addition to the official built-in plug-ins of APISIX, we can also customize the plug-ins according to our own needs. To customize the plug-ins, we need to use the Runner provided by APISIX. At present, the Runner that supports Java, Go and python languages is equivalent to the bridge between APISIX and custom plug-ins, For example, the project Apache APIs IX Python Runner can directly apply Python to the development of APIs IX plug-ins through Python Runner. The overall architecture is as follows:

Among them, Plugin Runner is the plug-in runner of each language. When Plugin Runner is configured, APISIX will start a sub process to run Plugin Runner, which belongs to the same user as APISIX process. When we restart or reload APISIX, Plugin Runner will also be restarted.

If you configure ext plugin - * plug-in for a given route, when the request hits the route, it will trigger APIs IX to initiate RPC calls to Plugin Runner through Unix Socket. The call is divided into two phases:

  • Ext plugin pre req: before executing APIs IX built-in plug-in
  • Ext plugin post req: after executing the APIs IX built-in plug-in

Next, let's take Python as an example to illustrate how to customize the plug-in. First, get the Apache APIs IX Python runner project:

➜ git clone
➜ cd apisix-python-plugin-runner
➜ git checkout 0.1.0  # Switch tool version 0.1.0

If it is in development mode, we can directly start Python Runner with the following command:

➜ APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock python3 apisix/ start

After startup, you need to add an external plug-in configuration in the apisik configuration file, as shown below:

➜ vim /path/to/apisix/conf/config.yaml
    - name: "admin"
      key: edd1c9f034335f136f87ad84b625c8f1
      role: admin

  path_for_test: /tmp/runner.sock

Through ext plugin path_ for_ Test specify the unix socket file path of Python Runner. If it is a production environment, you can use ext plugin CMD to specify the startup command of the Runner:

  cmd: [ "python3", "/path/to/apisix-python-plugin-runner/apisix/", "start" ]

Our APIs IX runs in the Kubernetes cluster, so to execute the Python Runner code in the Pod of APIs IX, we naturally need to put our Python code into the container of APIs IX, and then install the related dependencies of custom plug-ins, and directly add the above configuration in the configuration file of APIs IX, Therefore, we re customize the image containing plug-ins based on the image of APISIX, and add the following Dockerfile file in the root directory of APISIX Python plugin runner project:

FROM apache/apisix:2.10.0-alpine

ADD . /apisix-python-plugin-runner

RUN apk add --update python3 py3-pip && \
    cd /apisix-python-plugin-runner && \
    python3 -m pip install --upgrade pip && \
    python3 -m pip install -r requirements.txt --ignore-installed && \
    python3 install --force

Build a new image based on the Dockerfile above and push it to the Docker Hub:

➜ docker build -t cnych/apisix:py3-plugin-2.10.0-alpine .
# Push to DockerHub
➜ docker push cnych/apisix:py3-plugin-2.10.0-alpine

Next, we need to use the image built above to install apisik. We use Helm Chart to install here, so we need to overwrite it through the Values file, as shown below:

# ci/prod.yaml
  enabled: true

    repository: cnych/apisix
    tag: py3-plugin-2.10.0-alpine

Since the official Helm Chart does not provide support for ext plugin configuration, we need to manually modify the template file templates / configmap Yaml, add the configuration of ext plugin under the same level directory of apisik attribute, as shown below:

{{- if .Values.extPlugins.enabled }}
  {{- if .Values.extPlugins.pathForTest }}
  path_for_test: {{ .Values.extPlugins.pathForTest }}
  {{- end }}
  {{- if .Values.extPlugins.cmds }}
  {{- range $cmd := .Values.extPlugins.cmds }}
  - {{ $cmd }}
  {{- end }}
  {{- end }}
{{- end }}

  user: root  # fix does not have permission to execute python runner

Then add the following configuration in the customized Values file:

# ci/prod.yaml
  enabled: true
  cmds: ["python3", "/apisix-python-plugin-runner/apisix/", "start"]

Then you can redeploy APIs IX:

➜ helm upgrade --install apisix ./apisix -f ./apisix/ci/prod.yaml -n apisix

After deployment, you can see that a sub process of Python Runner will be started in the Pod of apisik:

In the plug-in directory / apifix Python plugin runner / apifix / plugins py files will be automatically loaded. In the above example, there are two plug-ins stop py and rewrite py, we use stop py as an example, the plug-in code is as follows:

from apisix.runner.plugin.base import Base
from apisix.runner.http.request import Request
from apisix.runner.http.response import Response

class Stop(Base):
    def __init__(self):
        super(Stop, self).__init__(self.__class__.__name__)

    def filter(self, request: Request, response: Response):
        # You can use ` self Config ` get the configuration information. If the plug-in is configured as JSON, it will be automatically converted to dictionary structure
        # print(self.config)

        # Set response Header
        response.headers["X-Resp-A6-Runner"] = "Python"
        # Set response body
        response.body = "Hello, Python Runner of APISIX"
        # Set response status code
        response.status_code = 201

        # By calling ` self Stop() ` interrupt the request process, and the client will respond to the request immediately
        # If not shown, call ` self Stop() ` or display call ` self Rewrite() ` will continue the request
        # The default is ` self rewrite()`

To implement the plug-in, you must first inherit the Base class and implement the filter function. The core business logic of the plug-in is in the filter function, which only contains Request and Response class objects as parameters. The Request object parameters can obtain the Request information, and the Response object parameters can set the Response information, self Config can get plug-in configuration information and call self. in filter function. Stop() will immediately interrupt the Request, respond to the data, and call self When rewrite(), the Request will continue.

Then we add a routing rule in the previous Nexus application to test the stop plug-in above, and add a routing rule in the ApisixRoute object, as shown below:

kind: ApisixRoute
  name: nexus
  namespace: default
    - name: ext
          - "/extPlugin"
      - name: ext-plugin-pre-req  # Enable ext plugin pre req plug-in
        enable: true
          - name: "stop"  # Use stop as a custom plug-in
            value: "{\"body\":\"hello\"}"
      - serviceName: nexus
        servicePort: 8081

Just create the above route directly. The core configuration is to enable the EXT plugin pre req plug-in (provided that the plug-in has been enabled in the configuration file and added to the Values of Helm Chart), and then use the conf attribute under config. Conf is an array format, multiple plug-ins can be set at the same time, and the name in the plug-in configuration object is the plug-in name, The name must be consistent with the plug-in code file and object name. value is the plug-in configuration and can be JSON string.

After creation, you can also see the routing configuration format in apisik in the Dashboard:

Then we can visit Use this path to verify our custom plug-in:

➜ curl -i
HTTP/1.1 201 Created
Date: Thu, 13 Jan 2022 07:04:50 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
accept: */*
user-agent: curl/7.64.1
X-Resp-A6-Runner: Python
Server: APISIX/2.10.0

Hello, Python Runner of APISIX

There is an x-resp-a6-runner in the access request result: Python header information, and the returned body data is hello, python runner of APIs IX, which is consistent with our definition in the plug-in. Here we have finished using Python to customize the APIs IX plug-in. If we have any business logic to deal with, we can directly define a corresponding plug-in.