Skip to content

feat: add standalone admin api#12179

Merged
bzp2010 merged 27 commits into
apache:masterfrom
bzp2010:bzp/feat-standalone-api
May 8, 2025
Merged

feat: add standalone admin api#12179
bzp2010 merged 27 commits into
apache:masterfrom
bzp2010:bzp/feat-standalone-api

Conversation

@bzp2010

@bzp2010 bzp2010 commented Apr 27, 2025

Copy link
Copy Markdown
Contributor

Description

This PR aims to add an Admin API specialized for Standalone mode to allow users to flush in-memory configurations through them.

The user can pass in the configuration as JSON or YAML via HTTP PUT and it will:

  1. Decode input
  2. Check each item in the input against the resource's JSON schema
  3. Broadcast them to every worker
  4. Load them, and APISIX takes effect.

This would make the stateless mode even more stateless, and it would no longer even depend on the file system. This will help our Ingress Controller use case.

In addition to the UPDATE API, there is also a GET API for dumping configurations, which will return the current configuration version and configuration contents.
However, the configuration content will be a deep copy replica of the input content, because APISIX will add internal data to the configuration, and since Lua passes table by reference, they will make it impossible to encode the data again. I.e., the data will be exported without any APISIX internal details, just to help you check what the current configuration is.

Scope

The scope of the API is all workers of the current APISIX instance, not the entire APISIX cluster. You need to call them on each instance to make them effective on the entire cluster.

The configuration takes effect in its entirety, so new commits will overwrite the old configuration in its entirety; there is no diff mechanism.

TODO

  • A health check API to indicate whether a configuration has been loaded to ensure that unconfigured pods do not receive traffic
  • Provide some mechanism to allow fine-grained management of conf_version. preventing destructiveness to routers and lrucache.
  • Add experimental mark to documentation to alert users that this is not production-ready and may be removed in the future.

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

@bzp2010
bzp2010 force-pushed the bzp/feat-standalone-api branch from f4061c4 to f150a56 Compare April 28, 2025 02:31
@bzp2010
bzp2010 marked this pull request as ready for review April 28, 2025 03:49
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Apr 28, 2025
Comment thread apisix/admin/init.lua Outdated
Comment thread t/pnpm-lock.yaml
Comment thread t/package.json
@@ -0,0 +1,16 @@
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about t/ts/package.json?

move all of the ts related files to folder t/ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reply in the next comment.

},
};

describe("Admin - Standalone", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a new way to write test cases

we can use test::nginx, got the same result, all right?

@bzp2010 bzp2010 Apr 28, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@membphis

This is insufficient for current testing, as the design does not share the configuration between workers in any way, but only broadcasts it through the event mechanism. Any restart of the worker may make the configuration disappear.

The test::nginx always uses reload or restarts the worker, which is bad. The current way is clear and runs entirely on a single instance, as it uses only one TEST section as a nutshell (which means the worker doesn't restart).

I know test::nginx allows no reload, but you can't modify the config because you don't want it to reload, and once you need to modify the config to add a new content_by_lua_block-like block, it has to reload.

This makes it impossible to accurately assert tests with inline lua code, which is detrimental to the tests I'm working on.

Why do I have to use Lua: I want to check a rather complex nested data structure, and regular expressions can't do it simply, or aren't readable. Not to mention the fact that cjson simply can't guarantee stable key ordering in JSON. So I wanted to implement it using Lua assert until I realized the above problem.

Another possible approach is to continue writing dirty shell scripts and treating them as cli tests, which requires us to check the response using similar external software such as jq or yq, which is also very bad. Very cumbersome and error prone.

So I prefer to write these tests in one of the most popular programming languages in the world, even typescript is far more popular to perl or shell, let alone javascript,

JavaScript TypeScript Bash/Shell Perl
64.6% 43.4% 34.2% 2.3%
The era of Perl is over.

which allows us to take full advantage of the various libraries available for direct asserting, such as jest, yaml, or other libraries that are currently in use.

This gives more people the opportunity to write tests without any confuses, rather than having to deal with strange test cases mixed with all sorts of weird perl, test::nginx scripts that are both hard to learn and hard to write. As a professional developer on APISIX, I too often have headaches because of this test framework.

Further, this code won't be included in the release at all, it's just a few tests, which won't bother users in any way, and this won't lead to a runtime dependency on nodejs, so I think that's the best route to take.

So, in the long-term, I'd like these ts-implemented tests to be first-class citizens, not stuffed into some folder. As a scripting language, js/ts is not fundamentally different from perl, anything that can be done with test::nginx can be done with them, even fine-grained manipulation of the nginx process for restart or reload, but it's just more popular, more modern, more expressive, better ecologically, and it's time to embrace the future, if only for a little while.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I do realize some additional issues, and I'm switching to shared dict to avoid the problem of not being able to resynchronize the configuration when one of the workers crashes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to shared dict

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test cases don't seem complicated, is there a reason to use JavaScript?

If we want to write new test cases, should we consider using test-nginx or JavaScript?

I am more inclined towards one of them, which is that introducing more language ecosystems actually increases complexity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlinsRan

Technically,

I've already explained the extent to which test::nginx doesn't meet my needs: the rigid perl DSL doesn't allow me to fine-tune the response data checker without introducing and learning new dependencies, and the use of content_by_lua blocks means that this forcefully couples the testing process with openresty's reload or restart, which would largely break the shared memory or event broadcast mechanism.

Instead of wasting time on things like Perl or test::nginx (which even implements an HTTP protocol parser of its own, why?), it's better to find another way out.
These tests can be based on any language, even with the efficient but primitive Go. Introducing new testing tools is all about solving ease-of-use and efficiency problems, which are not helped by using technologies with poor expressiveness (forced typing and barren standard libraries) and such a poor audience (two times as many professional developers use Java as use Go, three times as many professional developers use TypeScript as use Go, and four times as many professional developers use JavaScript as use Go).

If we want to write new test cases, should we consider using test-nginx or JavaScript?

There is no clear policy about it, and developers choose familiar technologies to complete their tests. This is not mandatory.

Right now, these new test sets don't have explicit internal APIs for things like modifying configurations, reloading, or restarting openresty, so they're lightweight and focused on testing APISIX for functional modifications using HTTP clients. Some of the more complex features can be introduced incrementally to allow us to implement more varied tests on nodejs until it is ready for the full range of professional developers' testing needs. At that point, we will consider stopping accept new tests using test::nginx.

As such, there is no risk of any immediate deprecation, and tests that already use test::nginx will continue to exist until they are rewritten more appropriately (if one so desires).

I am more inclined towards one of them, which is that introducing more language ecosystems actually increases complexity.

Continuing to invest in technologies that are outdated and hold no promise for development will cause us to accumulate more technical debt and get stuck in a quagmire.
The use of new technologies will bring efficiency gains and greatly increase the number of engineers capable of developing APISIX functionality, and TypeScript is a technology that stands at the right balance of efficiency, maintainability, and adoption.

I think the advantages will outweigh the disadvantages in terms of complexity, and the test framework will be implemented and maintained in the APISIX project so that users will only have to focus on how to write tests and not waste time exploring the boundaries of test::nginx functionality and debugging the odd glitch there (I've had enough of this).
Many features have more standard implementations, and the myriad of dependency libraries will support features that are not in the standard library. None of these are easily solved by Perl or test::nginx which is only a subset of the functionality, and with engineers being forced to learn it or quit APISIX development, I think the choice of which is simpler is obvious.


I don't want to try to solve these problems in-system, which have already been stomped into the dust by other technologies. I don't want to waste my time with technologies that have been buried up to their necks in dirt.

Although I can write code fluently consisting of test::nginx and solve any new problems I encounter. But I don't want to waste any more minutes on this mentally torturous ancient technology.


cc @membphis

Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
@bzp2010
bzp2010 requested a review from AlinsRan May 7, 2025 04:01
AlinsRan
AlinsRan previously approved these changes May 7, 2025

@kayx23 kayx23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

others LGTM

Comment thread docs/en/latest/deployment-modes.md Outdated
Comment thread docs/en/latest/deployment-modes.md Outdated
nic-6443
nic-6443 previously approved these changes May 7, 2025

@nic-6443 nic-6443 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no issues with this PR itself, but there is a TODO regarding this feature: In the API-driven standalone mode, we need to provide a health status endpoint that returns whether the current APISIX instance has loaded the configuration. This is because in the previous etcd mode and file-based standalone mode, when APISIX exposes ports, it can ensure that all configurations have been loaded. However, in the newly added mode, if such a health status endpoint is not provided, APISIX may expose services while external programs have not yet requested /apisix/admin/configs to submit configurations.

@bzp2010

bzp2010 commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

@nic-6443

if such a health status endpoint is not provided, APISIX may expose services while external programs have not yet requested /apisix/admin/configs to submit configurations.

This seems to be as it should be? The 9080 just returns 404 forever until the first configuration is committed.

@nic-6443

nic-6443 commented May 7, 2025

Copy link
Copy Markdown
Member

@nic-6443

if such a health status endpoint is not provided, APISIX may expose services while external programs have not yet requested /apisix/admin/configs to submit configurations.

This seems to be as it should be? The 9080 just returns 404 forever until the first configuration is committed.

@bzp2010
Yes, this is exactly what we need to avoid. External programs require time to know the new APISIX instance going online or an existing instance restarting. Before the external program commits the configuration for the first time, this APISIX instance does not have the capability to provide services externally. Therefore, we need a mechanism to provide the health status of APISIX, especially in this new API-driven standalone mode.

@bzp2010

bzp2010 commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

I think there is also a need to list this mode as experimental in the documentation. to warn users not to blindly use it in production environments, which could be removed before it becomes a GA feature.

Personally, I think this approach of coupling with APISIX standalone may not be sustainable, and we need to find some better way to serve stateless deployments than just what we have now, and the current implementation still has some limitations.

Given that there's still some time before the next release, I'd suggest adding it in the PR after that.

@bzp2010

bzp2010 commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

@nic-6443

Yes, this is exactly what we need to avoid. External programs require time to know the new APISIX instance going online or an existing instance restarting. Before the external program commits the configuration for the first time, this APISIX instance does not have the capability to provide services externally. Therefore, we need a mechanism to provide the health status of APISIX, especially in this new API-driven standalone mode.

Oh, I see. You are saying that this data plane should not report to Kubernetes that it is in a READY state to prevent the load balancer from distributing traffic to it until the ingress controller commits its first configuration.

membphis
membphis previously approved these changes May 7, 2025

@membphis membphis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only some minor issues

Comment thread apisix/admin/standalone.lua Outdated
if not ok then
return nil, "failed to save config to shared dict: " .. err
end
ngx.log(ngx.NOTICE, "standalone config updated: ", config)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls change to core.log.info(....)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread apisix/admin/standalone.lua Outdated
if err then
core.log.error("invalid request body: ", req_body, " err: ", err)
core.response.exit(400, {error_msg = "invalid request body: " .. err,
req_body = req_body})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it not safe to response the request body

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread apisix/admin/standalone.lua Outdated
if not valid then
core.log.error(err_prefix, err)
core.response.exit(400, {error_msg = err_prefix .. err,
req_body = req_body})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread apisix/admin/standalone.lua Outdated
if not valid then
core.log.error(err_prefix, err)
core.response.exit(400, {error_msg = err_prefix .. err,
req_body = req_body})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread t/discovery/consul_dump.t
"PUT /v1/agent/service/deregister/service_b1",
"PUT /v1/agent/service/deregister/service_b2",
"PUT /v1/agent/service/register\n" . "{\"Checks\": [{\"http\": \"http://baidu.com\",\"interval\": \"1s\"}],\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}",
"PUT /v1/agent/service/register\n" . "{\"Checks\": [{\"http\": \"https://1.1.1.1\",\"interval\": \"1s\"}],\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have to change this?

Seems unrelated to the current PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing will cause the CI to fail and needs to be replaced with an endpoint that is accessible in github workflow.

@bzp2010
bzp2010 dismissed stale reviews from membphis and nic-6443 via 4862fdb May 7, 2025 09:14

@kayx23 kayx23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc LGTM

@bzp2010
bzp2010 merged commit 1bac43a into apache:master May 8, 2025
laz-xyr pushed a commit to laz-xyr/apisix that referenced this pull request Jun 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

8 participants