猛然发现还没有看过 ingress-nginx 的代码,调查问题的时候都是直接阅读最终生成的 nginx.conf 文件。
使用较新版本的金丝雀发布功能时,发现 openresty 替换了原生的 nginx, nginx.conf 文件也发生了变化,文件中不直接包含 Pod 的 IP 地址,用一段 lua 脚本处理转发,需要阅读代码理清配置生成、Pod IP 的下发过程。
拓展:Kubernetes 基于 openresty 的 ingress-nginx 的实现分析和使用。
Nginx-ingress-controller 是用 Go 语言实现的,负责发现 Ingress 以及 Pod 的 IP 等,入口是:
核心操作在 NGINXController
中:
ngx := controller.NewNGINXController(conf, mc, fs)
go handleSigterm(ngx, func(code int) {
os.Exit(code)
})
创建 nginx controller 时,传入 syncQueue 的方法 n.syncIngress
负责生成并下发最新的配置:
// internal/ingress/controller/controller.go: 113
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *NGINXController {
...
n.syncQueue = task.NewTaskQueue(n.syncIngress)
...
阅读代码可知,配置变化分为可以动态加载和不可以动态加载的,只有配置变化不可以动态加载时,重新生成配置文件,并执行 reload 操作,代码中的!n.IsDynamicConfigurationEnough(pcfg)
部分,pcfg 是最新的相关配置:
动态加载的配置在函数 configureDynamically(pcfg) 中下发,调用了 openresty 的 /configuration 地址:
func configureDynamically(pcfg *ingress.Configuration) error {
...
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/backends", "application/json", backends)
...
statusCode, _, err = nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
...
在最终生成的 nginx.conf 可以找到 /configuration 接口的实现:
server {
listen unix:/tmp/nginx-status-server.sock;
set $proxy_upstream_name "internal";
...
location /configuration {
# this should be equals to configuration_data dict
client_max_body_size 10m;
client_body_buffer_size 10m;
proxy_buffering off;
content_by_lua_block {
configuration.call()
}
}
location / {
content_by_lua_block {
ngx.exit(ngx.HTTP_NOT_FOUND)
}
}
}
Lua 代码位于 rootfs/etc/ngxin/lua 目录中,在制作镜像的时候被一同打包到镜像中:
Lua 部分就是标准的 openresty 应用,openresty 的应用开发见 Web开发平台OpenResty。
编译:
make build
编译后得到的文件位于:
$ tree bin
bin
└── amd64
├── dbg
└── nginx-ingress-controller
制作镜像:
make container
制作镜像需要的文件位于 rootfs 中,制作镜像是准备一个临时目录,将 bin 目录中的文件和 rootfs 中的文件复制到临时目录中,并且替换 Dockerfile 中的 BASEIMAGE 等字符串,生成最终的 Dockerfile。
rootfs 目录中的所有文件都会被打包到容器中,所有如果要增减镜像中的文件,直接在 rootfs 目录中操作即可。