DevOps with API7 Enterprise Gateway, Declarative Configurations, and Pipelines

Introduction

In this document, we will guide you on how to set up Apache APISIX/API7 with Declarative Configurations Tool (ADC, APISIX Declarative Configuration CLI) in a Kubernetes environment. To automate the process, we will use the apisix.yaml file as the only source of truth, enabling us to leverage GitOps.

Keep in mind that certain actions have been highlighted in bold. This serves as a warning to exercise caution when working in the Production environment.

ADC Introduction

ADC takes the apisix.yaml file as the only source and transforms configurations into API Gateways accordingly. In other words, API gateways have the same state as described in apisix.yaml.

For more information, please refer to the https://github.com/api7/adc repository.

Scenarios

  1. Configure ADC and Connect ADC to Gateway Instances
  2. Publish Configurations
    1. Proxy Requests
    2. Key Authentication with Consumer & Plugin Enabled
  3. Modify Configurations and Apply Changes

Notes

  1. In this document, we are using API7 Enterprise Gateway v3.2.x, which is based on Apache APISIX 3.2 and does not yet provide Configuration Validation APIs. Therefore, we cannot utilize the adc validate command. This feature is available starting from Apache APISIX v3.5+.
  2. ADC supports loading your OpenAPI 3.0 spec file and converting it to apisix.yaml. However, you should update the converted file accordingly. For example, you need to add Authentication policies and bind a Service ID. We have plans to improve these functionalities for better user experience.
  3. Enterprises may have different Git/SVN version control services and firewall policies. However, as long as the ADC tool can access Gateway’s Admin APIs, it will work. This document provides a demonstration of how to operate the ADC on a local computer.

Prerequisites

  • Kubernetes: Kubernetes 1.28.2
    • Note: API7 Enterprise doesn’t restrict Kubernetes version. This document runs Kubernetes on DigitalOcean.
  • API7 Enterprise Gateway
    • Note: API7.ai provides Docker image and Helm chart.
  • ADC: 0.4.3

Deploy API7 Gateway on Kubernetes

$ helm repo add api7 https://charts.api7.ai

$ helm repo update api7

$ helm install api7 api7/api7 \
  --create-namespace -n api7 \ 
  # Note: To request for an IP and receive external traffic
  --set gateway.type=LoadBalancer \
  # Note: Define your random Admin API Key     
  --set admin.credentials.admin=**edd1c9f034335f136f87ad84b625c8f1** \
  # Note: To allow ADC to connect to Admin APIs. **(Only for Testing)**
  --set admin.type=**LoadBalancer** \
  # Note: To allow ADC to access Admin APIs externally **(Only for Testing)**
  --set admin.allow.ipList\[0\]=**0.0.0.0/0**

NAME: api7
LAST DEPLOYED: Wed Oct 18 16:06:17 2023
NAMESPACE: api7
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace api7 -o jsonpath="{.spec.ports[0].nodePort}" services api7-gateway)
  export NODE_IP=$(kubectl get nodes --namespace api7 -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
  
$ kubectl get pods -n api7

NAME                    READY   STATUS    RESTARTS   AGE
api7-5f7dd98c45-2k88s   1/1     Running   0          6m34s
api7-etcd-0             1/1     Running   0          6m34s
api7-etcd-1             1/1     Running   0          6m34s
api7-etcd-2             1/1     Running   0          6m34s

$ kubectl get svc -n api7

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)             AGE
api7-admin           LoadBalancer   10.245.61.242    **146.190.5.177**   9180:31517/TCP      6m53s
api7-etcd            ClusterIP      10.245.88.194    <none>          2379/TCP,2380/TCP   6m53s
api7-etcd-headless   ClusterIP      None             <none>          2379/TCP,2380/TCP   6m53s
api7-gateway         LoadBalancer   10.245.162.181   **167.172.6.103**   80:31584/TCP        6m53s

Start Receiving Traffic

To receive traffic, we should set API7 Gateway as a LoadBalancer.

# Note: Because we have not configured any Routes yet, it should return Error 404 Not Found.

$curl -I http://167.172.6.103:80

HTTP/1.1 404 Not Found
Date: Wed, 18 Oct 2023 09:27:33 GMT
Content-Type: text/plain; charset=utf-8
Connection: keep-alive
Server: API7/3.2.2

Scenarios

Configure ADC

We need to verify Admin APIs are accessible.

$curl http://146.190.5.177:9180/apisix/admin/routes -H"X-API-Key: edd1c9f034335f136f87ad84b625c8f1"

{"list":[],"total":0}

Download ADC: Please visit https://github.com/api7/adc/releases to download the latest version.

# Note: I'm using Mac M1

$ ./adc -h

A command line interface for configuring APISIX declaratively.

It can be used to validate, dump, diff, and sync configurations with an APISIX instance.

Usage:
  adc [command]

Available Commands:
  completion     Generate the autocompletion script for the specified shell
  configure      Configure ADC with APISIX instance
  diff           Show the differences between the local and existing APISIX configuration
  dump           Dump the APISIX configuration
  help           Help about any command
  openapi2apisix Convert OpenAPI configuration to ADC configuration
  ping           Verify connectivity with APISIX
  sync           Sync local configuration to APISIX
  validate       Validate the provided configuration file
  version        Print the version of ADC

Flags:
      --config string   config file (default is $HOME/.adc.yaml)
  -h, --help            help for adc

Use "adc [command] --help" for more information about a command.

Configure ADC:

# Note: We have plans to support passing values via --flag

$echo"**http://146.190.5.177:9180**\n**edd1c9f034335f136f87ad84b625c8f1**\n" | ./adc configure -f

Config file not found at /Users/username/.adc.yaml. Creating...
Please enter the APISIX server address:
Please enter the APISIX token:
ADC configured successfully!

Ping API Gateway Instances:

$./adc ping

Connected to backend successfully!

Publish Configurations

Create the apisix.yaml file:

name: "Example configuration"
version: "1.0.0"
routes:
  **- id: httpbin-route-anything**
    methods:
      - GET
    name: httpbin-route-anything
    service_id: httpbin-service
    status: 1
    uri: /anything
  **- id: httpbin-route-ip**
    methods:
      - GET
    name: httpbin-route-ip
    service_id: httpbin-service
    status: 1
    uri: /ip
  **- id: httpbin-route-protected-uuid**
    methods:
      - GET
    name: httpbin-route-protected-uuid
    service_id: httpbin-service
    plugins:
      key-auth:
        _meta:
          disable: false
    status: 1
    uri: /uuid
services:
  - hosts:
      **- api7.ai**
    id: httpbin-service
    name: httpbin-service
    upstream:
      hash_on: vars
      id: httpbin
      name: httpbin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1
      pass_host: pass
      scheme: http
      type: roundrobin
consumers:
  - username: tom
    plugins:
      key-auth:
        key: tomskey

Sync apisix.yaml to gateway instances

$./adcsync -f ./apisix.yaml

creating consumer: "tom"
creating service: "httpbin-service"
creating route: "httpbin-route-anything"
creating route: "httpbin-route-ip"
creating route: "httpbin-route-protected-uuid"
Summary: created 5, updated 0, deleted 0

Proxy Validation:

$curl 167.172.6.103/anything -H"Host: api7.ai" -v

*   Trying 167.172.6.103:80...
* Connected to 167.172.6.103 (167.172.6.103) port 80 (#0)
> GET /anything HTTP/1.1
> Host: api7.ai
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 382
< Connection: keep-alive
< Date: Thu, 19 Oct 2023 03:38:58 GMT
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< Server: API7/3.2.2
<
{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Host": "api7.ai",
    "User-Agent": "curl/8.1.2",
    "X-Amzn-Trace-Id": "Root=1-6530a4d2-0b74b9701a0367d758805510",
    **"X-Forwarded-Host": "api7.ai"**
  },
  "json": null,
  "method": "GET",
  "origin": "10.104.0.2, 143.198.94.230",
  "url": "http://api7.ai/anything"
}

Plugins Validation: Key Authentication

$curl 167.172.6.103/uuid -H"Host: api7.ai"

{"message":"Missing API key found in request"}

$curl 167.172.6.103/uuid -H"Host: api7.ai" -H"apikey: tomskey"

{
  "uuid": "16abc3ad-dd3d-415e-a22c-e768753fd04c"
}

Modify Configurations and Apply Changes

Update the apisix.yaml to the following configurations:

name: "Example configuration"
version: "1.0.0"
routes:
  - id: httpbin-route-anything
    methods:
      - GET
    name: httpbin-route-anything
    service_id: httpbin-service
    status: 1
    uri: /anything
  - id: httpbin-route-ip
    methods:
      - GET
    name: httpbin-route-ip
    service_id: httpbin-service
    status: 1
    uri: /ip
  - id: httpbin-route-protected-uuid
    methods:
      - GET
    name: httpbin-route-protected-uuid
    service_id: httpbin-service
    plugins:
      key-auth:
        _meta:
          disable: false
    status: 1
    uri: /uuid
services:
  - hosts:
      **- httpbin**
    id: httpbin-service
    name: httpbin-service
    upstream:
      hash_on: vars
      id: httpbin
      name: httpbin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1
      pass_host: pass
      scheme: http
      type: roundrobin
consumers:
  - username: tom
    plugins:
      key-auth:
        key: tomskey

Determine the differences between local configurations and remote configurations:

$ ./adc diff -f ./apisix.yaml

update service: "httpbin-service"
--- remote
+++ local
@@ -2,7 +2,7 @@
        "id": "httpbin-service",
        "name": "httpbin-service",
        "hosts": [
-               "api7.ai"
+               "httpbin"
        ],
        "upstream": {
                "id": "httpbin",

Summary: create 0, update 1, delete 0

Apply Changes:

$ ./adc sync -f ./apisix.yaml

updating service: "httpbin-service"
--- remote
+++ local
@@ -2,7 +2,7 @@
         "id": "httpbin-service",
         "name": "httpbin-service",
         "hosts": [
-                "api7.ai"
+                "httpbin"
         ],
         "upstream": {
                 "id": "httpbin",

Summary: created 0, updated 1, deleted 0

Validation:

# Note: Because we have changed the Host attribute, it should return 404.

$curl 167.172.6.103/anything -H"Host: api7.ai"

{"error_msg":"404 Route Not Found"}

$curl 167.172.6.103/anything -H"Host: httpbin" -v

*   Trying 167.172.6.103:80...
* Connected to 167.172.6.103 (167.172.6.103) port 80 (#0)
> GET /anything HTTP/1.1
> Host: httpbin
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 382
< Connection: keep-alive
< Date: Thu, 19 Oct 2023 03:39:55 GMT
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< Server: API7/3.2.2
<
{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin",
    "User-Agent": "curl/8.1.2",
    "X-Amzn-Trace-Id": "Root=1-6530a50b-4bff905e6dd3bb95674698d9",
    **"X-Forwarded-Host": "httpbin"**
  },
  "json": null,
  "method": "GET",
  "origin": "10.104.0.3, 143.198.94.230",
  "url": "http://httpbin/anything"
}

Q&A

How to upgrade chart?

$ helm install api7 api7/api7 --create-namespace -n api7 --set apisix.image.pullPolicy="Always"