How to use Opensource Apache APISIX as an API Gateway

How to use Opensource Apache APISIX as an API Gateway

apache_apisix

Here in this article we are going to install and configure the APISIX services and route the traffic using the API Gateway to backend python flask based microservices applications.

Test Environment

Fedora 37 server
Docker
Docker Compose
curl

What is Apache APISIX

Apache APISIX is an Opensource API Gateway Management tool. It provides us with traffic handling for websites, mobile and IoT applications by providing services such as load balancing, dynamic upstream, canary release, fine-grained routing, rate limiting, and many more. It is a dynamic, scalable and high performance cloud native API gateway for API’s and microservices.

If you are interested in watching the video, Here is the YouTube video on the same step by step procedure outline below.

Procedure

Step1: Ensure Docker Installed and Running

As a pre-requisite step ensure that docker and docker-compose is installed and running. Also we will required curl for sending the http requests to the API gateway.

[admin@fedser apisix]$ docker -v
Docker version 23.0.1, build a5ee5b1

[admin@fedser apisix]$ docker-compose -v
docker-compose version 1.29.2, build 5becea4c

[admin@fedser apisix]$ curl --version
curl 7.85.0 (x86_64-redhat-linux-gnu) libcurl/7.85.0 OpenSSL/3.0.8 zlib/1.2.12 brotli/1.0.9 libidn2/2.3.4 libpsl/0.21.1 (+libidn2/2.3.3) libssh/0.10.4/openssl/zlib nghttp2/1.51.0
[admin@fedser apisix]$ sudo systemctl status docker.service 
[sudo] password for admin: 
‚óŹ docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; preset: disabled)
     Active: active (running) since Tue 2023-02-28 11:53:22 IST; 4h 30min ago

Step2: Launch Microservices Flask Applications

Here in this step we are going to install flask python package and create two flask based microservices applications. These two application will be serving on two different ports 2020 and 2021 with contexts /django and /kivy respectively as shown below.

Ensure flask python package is installed

[admin@fedser apisix]$ mkdir flask; cd flask
[admin@fedser flask]$ pip install Flask

Create two flask based applications

[admin@fedser flask]$ cat hellodjango.py 
from flask import Flask

app = Flask(__name__)

@app.route("/django")
def hello_world():
    return "<p>Hello, Django!</p>"
[admin@fedser flask]$ cat hellokivy.py 
from flask import Flask

app = Flask(__name__)

@app.route("/kivy")
def hello_world():
    return "<p>Hello, Kivy!</p>"

Launch flask applications

[admin@fedser flask]$ flask --app hellodjango run --host=0.0.0.0 --port=2020 &
[admin@fedser flask]$ flask --app hellokivy run --host=0.0.0.0 --port=2021 &

Verify flask applications

http://192.168.29.117:2020/django
http://192.168.29.117:2021/kivy

NOTE: Replace 192.168.29.117 with your host FQDN

Step3: Create Docker Compose file for APISIX Services

Here we have created a docker-compose.yml file which would launch three APISIX service related components (ie. apisix, apisix-dashboard, etcd). APISIX uses etcd to save and synchronize configuration. Before installing APISIX, you need to install etcd on your machine.

ServicePorts
apisix-dashboard9000
apisix9180, 9080, 9091, 9443, 9092
etcd2379

Docker Compose


[admin@fedser apisix]$ cat docker-compose.yml 
version: "3"

services:
  apisix-dashboard:
    image: apache/apisix-dashboard:3.0.0-alpine
    restart: always
    volumes:
    - ./dashboard_conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml
    ports:
    - "9000:9000"
    networks:
      apisix:

  apisix:
    image: apache/apisix:${APISIX_IMAGE_TAG:-3.1.0-debian}
    restart: always
    volumes:
      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
    depends_on:
      - etcd
    ##network_mode: host
    ports:
      - "9180:9180/tcp"
      - "9080:9080/tcp"
      - "9091:9091/tcp"
      - "9443:9443/tcp"
      - "9092:9092/tcp"
    networks:
      apisix:

  etcd:
    image: bitnami/etcd:3.4.15
    restart: always
    volumes:
      - etcd_data:/bitnami/etcd
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
    ports:
      - "2379:2379/tcp"
    networks:
      apisix:

networks:
  apisix:
    driver: bridge

volumes:
  etcd_data:
    driver: local

APISIX Dashboard config

[admin@fedser apisix]$ cat dashboard_conf/conf.yaml 
...
conf:
  listen:
    host: 0.0.0.0     # `manager api` listening ip or host name
    port: 9000          # `manager api` listening port
  allow_list:           # If we don't set any IP list, then any IP access is allowed by default.
    - 0.0.0.0/0
  etcd:
    endpoints:          # supports defining multiple etcd host addresses for an etcd cluster
      - "http://etcd:2379"
                          # yamllint disable rule:comments-indentation
                          # etcd basic auth info
    # username: "root"    # ignore etcd username if not enable etcd auth
    # password: "123456"  # ignore etcd password if not enable etcd auth
    mtls:
      key_file: ""          # Path of your self-signed client side key
      cert_file: ""         # Path of your self-signed client side cert
      ca_file: ""           # Path of your self-signed ca cert, the CA is used to sign callers' certificates
    # prefix: /apisix     # apisix config's prefix in etcd, /apisix by default
  log:
    error_log:
      level: warn       # supports levels, lower to higher: debug, info, warn, error, panic, fatal
      file_path:
        logs/error.log  # supports relative path, absolute path, standard output
                        # such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr
    access_log:
      file_path:
        logs/access.log  # supports relative path, absolute path, standard output
                         # such as: logs/access.log, /tmp/logs/access.log, /dev/stdout, /dev/stderr
                         # log example: 2020-12-09T16:38:09.039+0800	INFO	filter/logging.go:46	/apisix/admin/routes/r1	{"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []}
  security:
      # access_control_allow_origin: "http://httpbin.org"
      # access_control_allow_credentials: true          # support using custom cors configration
      # access_control_allow_headers: "Authorization"
      # access_control-allow_methods: "*"
      # x_frame_options: "deny"
      content_security_policy: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-src *"  # You can set frame-src to provide content for your grafana panel.

authentication:
  secret:
    secret              # secret for jwt token generation.
                        # NOTE: Highly recommended to modify this value to protect `manager api`.
                        # if it's default value, when `manager api` start, it will generate a random string to replace it.
  expire_time: 3600     # jwt token expire time, in second
  users:                # yamllint enable rule:comments-indentation
    - username: admin   # username and password for login `manager api`
      password: admin
    - username: user
      password: user

plugins:                          # plugin list (sorted in alphabetical order)
  - api-breaker
  - authz-keycloak
  - basic-auth
  - batch-requests
  - consumer-restriction
  - cors
  # - dubbo-proxy
  - echo
  # - error-log-logger
  # - example-plugin
  - fault-injection
  - grpc-transcode
  - hmac-auth
  - http-logger
  - ip-restriction
  - jwt-auth
  - kafka-logger
  - key-auth
  - limit-conn
  - limit-count
  - limit-req
  # - log-rotate
  # - node-status
  - openid-connect
  - prometheus
  - proxy-cache
  - proxy-mirror
  - proxy-rewrite
  - redirect
  - referer-restriction
  - request-id
  - request-validation
  - response-rewrite
  - serverless-post-function
  - serverless-pre-function
  # - skywalking
  - sls-logger
  - syslog
  - tcp-logger
  - udp-logger
  - uri-blocker
  - wolf-rbac
  - zipkin
  - server-info
  - traffic-split

APISIX Config

[admin@fedser apisix]$ cat apisix_conf/config.yaml 
...
apisix:
  node_listen: 9080              # APISIX listening port
  enable_ipv6: false

  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092

deployment:
  admin:
    allow_admin:               # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
      - 0.0.0.0/0              # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

    admin_key:
      - name: "admin"
        key: edd1c9f034335f136f87ad84b625c8f1
        role: admin                 # admin: manage all configuration data

      - name: "viewer"
        key: 4054f7cf07e344346cd3f287985e76a2
        role: viewer

  etcd:
    host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
      - "http://etcd:2379"          # multiple etcd address
    prefix: "/apisix"               # apisix configurations prefix
    timeout: 30                     # 30 seconds

plugin_attr:
  prometheus:
    export_addr:
      ip: "0.0.0.0"
      port: 9091

Step4: Start the APISIX services

Now that our configuration files are ready along with the docker-compose.yml file. Let’s start all the APISIX related services as shown below.

[admin@fedser apisix]$ docker-compose up -d
Creating network "apisix_apisix" with driver "bridge"
Creating volume "apisix_etcd_data" with local driver
Creating apisix_etcd_1             ... done
Creating apisix_apisix-dashboard_1 ... done
Creating apisix_apisix_1           ... done
[admin@fedser apisix]$ docker-compose ps
          Name                         Command               State                                                             Ports                                                          
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
apisix_apisix-dashboard_1   /usr/local/apisix-dashboar ...   Up      0.0.0.0:9000->9000/tcp,:::9000->9000/tcp                                                                                 
apisix_apisix_1             /docker-entrypoint.sh dock ...   Up      0.0.0.0:9080->9080/tcp,:::9080->9080/tcp, 0.0.0.0:9091->9091/tcp,:::9091->9091/tcp,                                      
                                                                     0.0.0.0:9092->9092/tcp,:::9092->9092/tcp, 0.0.0.0:9180->9180/tcp,:::9180->9180/tcp,                                      
                                                                     0.0.0.0:9443->9443/tcp,:::9443->9443/tcp                                                                                 
apisix_etcd_1               /opt/bitnami/scripts/etcd/ ...   Up      0.0.0.0:2379->2379/tcp,:::2379->2379/tcp, 2380/tcp  

Step5: Ensure APISIX service running

We can validate whether the APISIX serivce is up and running by the following API request as shown below.

[admin@fedser apisix]$ curl "http://192.168.29.117:9180/apisix/admin/services/" -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
{"total":0,"list":[]}

Step6: Create upstreams for two backend flask applications

Upstream is the service to forward your requests to. They can be configured to a Route or abstracted out to an Upstream object.

Request

[admin@fedser apisix]$ curl "http://192.168.29.117:9180/apisix/admin/upstreams/1" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "192.168.29.117:2020": 1
  }
}'

Response

{"key":"\/apisix\/upstreams\/1","value":{"type":"roundrobin","hash_on":"vars","id":"1","update_time":1677584079,"scheme":"http","pass_host":"pass","nodes":{"192.168.29.117:2020":1},"create_time":1677584079}}

Request

[admin@fedser apisix]$ curl "http://192.168.29.117:9180/apisix/admin/upstreams/2" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "192.168.29.117:2021": 2
  }
}'

Response

{"key":"\/apisix\/upstreams\/2","value":{"type":"roundrobin","hash_on":"vars","id":"2","update_time":1677584123,"scheme":"http","pass_host":"pass","nodes":{"127.0.0.1:2021":2},"create_time":1677584123}}

Step7: Create routes for two backend flask applications

Routes specify how requests to APISIX are forwarded to the Upstream. They match a client’s request based on defined rules and loads and executes the configured Plugins.

Request

[admin@fedser apisix]$ curl "http://192.168.29.117:9180/apisix/admin/routes/1" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "methods": ["GET"],
  "host": "example.com",
  "uri": "/django",
  "upstream_id": "1"
}'

Response

{"key":"\/apisix\/routes\/1","value":{"update_time":1677584365,"status":1,"create_time":1677584365,"uri":"\/django","host":"example.com","priority":0,"id":"1","methods":["GET"],"upstream_id":"1"}}

Request

[admin@fedser apisix]$ curl "http://192.168.29.117:9180/apisix/admin/routes/2" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
  "methods": ["GET"],
  "host": "example.com",
  "uri": "/kivy",
  "upstream_id": "2"
}'

Response

{"key":"\/apisix\/routes\/2","value":{"update_time":1677584422,"status":1,"create_time":1677584422,"uri":"\/kivy","host":"example.com","priority":0,"id":"2","methods":["GET"],"upstream_id":"2"}}

Step8: Test the Services

Now that our upstream services are configured and the routes created to route the traffic from APISIX gateway to the Upstream objects. We can validate if we are able to send requests to the APISIX gateway so that the requests are routed appropriately to the backend API upstream services as per the configuration.

[admin@fedser apisix]$ curl -i -X GET "http://192.168.29.117:9080/django" -H "Host: example.com"
192.168.48.4 - - [28/Feb/2023 17:12:59] "GET /django HTTP/1.1" 200 -
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 21
Connection: keep-alive
Date: Tue, 28 Feb 2023 11:42:59 GMT
Server: APISIX/3.1.0

<p>Hello, Django!</p>
[admin@fedser apisix]$ curl -i -X GET "http://192.168.29.117:9080/kivy" -H "Host: example.com"
192.168.48.4 - - [28/Feb/2023 17:13:20] "GET /kivy HTTP/1.1" 200 -
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 19
Connection: keep-alive
Date: Tue, 28 Feb 2023 11:43:20 GMT
Server: APISIX/3.1.0

<p>Hello, Kivy!</p>

APISIX will forward the request to http://127.0.0.1:2020/django and http://127.0.0.1:2020/kivy respectively.

Step9: Validate Upstreams and Route using APISIX Dashboard

We can access the APISIX Dashboard using the following details. Once logged in you can verify the the Routes and Upstreams that we created earlier as shown in below screenshots.

URL - http://192.168.29.117:9000/
username - admin
password - admin

Hope you enjoyed reading this article. Thank you..