ingress-nginx插件开发:自定义控制器扩展
引言
你是否曾经遇到过这样的场景:标准的Kubernetes Ingress功能无法满足你的特殊需求?或者你想要为ingress-nginx添加一些自定义的逻辑来处理特定的流量路由、认证或监控需求?这正是ingress-nginx插件开发的用武之地。
本文将深入探讨ingress-nginx的插件开发体系,从架构设计到具体实现,为你提供一套完整的自定义控制器扩展方案。通过阅读本文,你将掌握:
- ingress-nginx的核心架构和工作原理
- 插件开发的三种主要方式:Lua插件、Go扩展和Annotation驱动
- 实战案例:构建自定义认证插件
- 调试和测试策略
- 生产环境部署最佳实践
ingress-nginx架构深度解析
核心组件架构
配置生成流程
ingress-nginx的配置生成遵循以下流程:
- 监控阶段:控制器监控Kubernetes API中的Ingress、Service、Endpoints等资源变化
- 解析阶段:解析Ingress注解和规则,生成内部配置模型
- 模板渲染:使用Go模板将配置模型渲染为nginx.conf
- 验证重载:验证配置语法并触发NGINX重载
插件开发的三驾马车
1. Lua插件开发
Lua插件是ingress-nginx最灵活的扩展方式,主要在请求处理阶段介入。
Lua插件架构
基础Lua插件模板
-- /etc/nginx/lua/custom_plugin.lua
local _M = {}
function _M.access()
local headers = ngx.req.get_headers()
-- 自定义访问控制逻辑
if headers["X-Custom-Auth"] ~= "secret-token" then
ngx.exit(ngx.HTTP_FORBIDDEN)
return
end
-- 添加自定义请求头
ngx.req.set_header("X-Custom-Processed", "true")
end
function _M.header_filter()
-- 添加自定义响应头
ngx.header["X-Custom-Response"] = "processed-by-custom-plugin"
end
return _M
2. Go语言扩展开发
对于需要深度集成或高性能需求的场景,Go扩展是更好的选择。
控制器扩展点
ingress-nginx提供了多个可扩展的接口:
// 自定义注解处理器示例
package customannotation
import (
"fmt"
"net/http"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type customConfig struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
}
// Parser实现了注解解析接口
type Parser struct {
r resolver.Resolver
}
// NewParser创建新的注解解析器
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return &Parser{r}
}
// Parse解析注解配置
func (p *Parser) Parse(ing parser.AnnotationInterface) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation("custom-plugin/enabled", ing)
if err != nil {
return nil, err
}
mode, err := parser.GetStringAnnotation("custom-plugin/mode", ing)
if err != nil {
mode = "default"
}
return &customConfig{
Enabled: enabled,
Mode: mode,
}, nil
}
// GetFieldDescription返回字段描述
func (p *Parser) GetFieldDescription() string {
return "Custom plugin configuration annotations"
}
3. Annotation驱动扩展
通过注解配置现有功能是最轻量级的扩展方式。
常用注解模式
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
# 自定义配置注解
nginx.ingress.kubernetes.io/custom-plugin-enabled: "true"
nginx.ingress.kubernetes.io/custom-plugin-mode: "strict"
nginx.ingress.kubernetes.io/custom-plugin-config: |
{
"rateLimit": "100r/s",
"whitelist": ["192.168.1.0/24"]
}
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
实战案例:构建JWT认证插件
需求分析
我们需要为内部API添加JWT(JSON Web Token)认证层,要求:
- 支持多种JWT验证算法(HS256, RS256)
- 可配置的令牌黑名单
- 详细的认证日志记录
- 优雅的认证失败处理
架构设计
Lua实现代码
local cjson = require "cjson"
local jwt = require "resty.jwt"
local _M = {}
local plugin_config = {
secret_key = os.getenv("JWT_SECRET") or "default-secret",
algorithm = "HS256",
blacklist = {}
}
function _M.init()
-- 从ConfigMap加载黑名单
local blacklist_file = "/etc/nginx/jwt-blacklist.json"
local file = io.open(blacklist_file, "r")
if file then
local content = file:read("*a")
file:close()
plugin_config.blacklist = cjson.decode(content) or {}
end
end
function _M.validate_token(token)
if not token then
return false, "Missing token"
end
-- 检查黑名单
for _, black_token in ipairs(plugin_config.blacklist) do
if token == black_token then
return false, "Token in blacklist"
end
end
-- 验证JWT
local jwt_obj = jwt:verify(plugin_config.secret_key, token)
if not jwt_obj.verified then
return false, "Invalid token: " .. (jwt_obj.reason or "unknown")
end
return true, jwt_obj.payload
end
function _M.access()
local auth_header = ngx.var.http_Authorization
if not auth_header then
ngx.header["WWW-Authenticate"] = 'Bearer realm="API"'
ngx.exit(ngx.HTTP_UNAUTHORIZED)
return
end
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
if not token then
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
local valid, claims_or_error = _M.validate_token(token)
if not valid then
ngx.log(ngx.WARN, "JWT validation failed: " .. claims_or_error)
ngx.exit(ngx.HTTP_FORBIDDEN)
return
end
-- 将声明信息传递给上游服务
ngx.req.set_header("X-JWT-Claims", cjson.encode(claims_or_error))
end
return _M
Go配置解析器
package jwtannotation
import (
"encoding/json"
"fmt"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type JWTConfig struct {
Enabled bool `json:"enabled"`
SecretKey string `json:"secretKey"`
Algorithm string `json:"algorithm"`
}
type Parser struct {
r resolver.Resolver
}
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return &Parser{r}
}
func (p *Parser) Parse(ing parser.AnnotationInterface) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation("jwt-auth/enabled", ing)
if err != nil {
enabled = false
}
secretKey, err := parser.GetStringAnnotation("jwt-auth/secret-key", ing)
if err != nil {
return nil, fmt.Errorf("JWT secret key is required when enabled")
}
algorithm, err := parser.GetStringAnnotation("jwt-auth/algorithm", ing)
if err != nil {
algorithm = "HS256"
}
return &JWTConfig{
Enabled: enabled,
SecretKey: secretKey,
Algorithm: algorithm,
}, nil
}
func (p *Parser) GetFieldDescription() string {
return "JWT authentication configuration"
}
调试与测试策略
本地开发环境搭建
# 使用minikube搭建测试环境
minikube start --driver=docker
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
# 构建自定义镜像
docker build -t custom-ingress-nginx:latest -f rootfs/Dockerfile .
# 部署测试
kubectl set image deployment/ingress-nginx-controller \
controller=custom-ingress-nginx:latest -n ingress-nginx
调试技巧
-- 调试日志输出
local function debug_log(message, data)
if ngx.var.debug_mode == "1" then
if data then
ngx.log(ngx.DEBUG, message .. ": " .. require("cjson").encode(data))
else
ngx.log(ngx.DEBUG, message)
end
end
end
-- 性能监控
local function monitor_performance(name)
local start_time = ngx.now()
return function()
local elapsed = ngx.now() - start_time
ngx.log(ngx.INFO, name .. " took " .. elapsed .. " seconds")
end
end
单元测试示例
func TestJWTAnnotationParser(t *testing.T) {
parser := NewParser(nil)
testCases := []struct {
name string
annotations map[string]string
expected *JWTConfig
shouldError bool
}{
{
name: "valid configuration",
annotations: map[string]string{
"jwt-auth/enabled": "true",
"jwt-auth/secret-key": "test-secret",
"jwt-auth/algorithm": "HS256",
},
expected: &JWTConfig{
Enabled: true,
SecretKey: "test-secret",
Algorithm: "HS256",
},
shouldError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ing := &testIngress{annotations: tc.annotations}
result, err := parser.Parse(ing)
if tc.shouldError {
if err == nil {
t.Errorf("Expected error but got none")
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
config := result.(*JWTConfig)
if config.Enabled != tc.expected.Enabled {
t.Errorf("Expected Enabled %v, got %v", tc.expected.Enabled, config.Enabled)
}
})
}
}
性能优化与最佳实践
性能优化策略
| 优化点 | 策略 | 预期效果 |
|---|---|---|
| Lua代码优化 | 避免全局变量,使用local | 减少内存占用,提高执行速度 |
| 缓存机制 | 实现LRU缓存频繁验证结果 | 减少重复计算,降低延迟 |
| 连接池 | 使用cosocket连接池 | 减少连接建立开销 |
| 异步处理 | 使用ngx.timer.at处理耗时操作 | 避免阻塞请求处理 |
生产环境部署清单
-
资源限制
resources: limits: cpu: "2" memory: "1Gi" requests: cpu: "500m" memory: "256Mi" -
健康检查配置
livenessProbe: httpGet: path: /healthz port: 10254 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /healthz port: 10254 initialDelaySeconds: 5 periodSeconds: 5 -
监控指标
# Prometheus监控指标 # HELP nginx_ingress_controller_requests Total number of requests # TYPE nginx_ingress_controller_requests counter
常见问题与解决方案
问题1:插件性能影响过大
症状:请求延迟明显增加,NGINX worker进程CPU使用率高
解决方案:
- 使用ngx.now()进行性能分析,定位瓶颈
- 优化Lua代码,避免不必要的字符串操作
- 实现缓存机制减少重复计算
- 考虑将耗时操作移到Go层面
问题2:内存泄漏
症状:内存使用持续增长,最终导致OOM(Out Of Memory)
解决方案:
- 定期检查Lua中的全局变量使用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



