基于阿里云的 ingress-nginx 外部认证实战【部分未完成】

文章目录

    • @[toc]
    • 基本概念 - K8S的外部访问方式
      • NodePort
      • LoadBalancer
      • Ingress
    • Ingress 概念
      • Ingress 简介
      • Ingress && ingress-controllers && ingress-class 基本概念
      • Ingress-nginx 原理说明
        • ingress-controllers 的内部构成
        • ingress-controllers 的整体运行逻辑
        • ingress-controllers 内部细化逻辑
        • ingress-controllers 重载配置逻辑
        • 客户端通过 Ingress 访问 Pod 的流程
    • ingress 资源清单版本差异说明
    • 常用注解说明
    • ingress-nginx 自定义组件开发
      • ngx_http_lua_module
      • openresty 执行指令(过滤器)
      • ingress-nginx 支持的过滤器
      • 源码解读
      • 扩展组件
        • 扩展组件 && 第三方库 存放位置
        • 扩展组件 编写
        • 安装扩展组件 (构建镜像)
        • 启用扩展组件
    • 项目实战
      • 项目概述
      • 外部认证原理
        • (1) WAF 防火墙
        • (2) api-go-v3
        • (3) api-php-v1
        • (4) 外部缓存存储
        • (5) 外部认证服务
        • (6) Ingress 控制器
      • 测试流程
        • 检查相关 Pod 的运行状态
        • 检查 SVC 的启动状态
          • api-go-v3
          • api-php-v1
          • auth-token-resolve-v1
          • token-cache
        • 整体测试
    • 阿里云 - ACK下Ingress-nginx的日志收集
      • 整体流程描述
      • 日志格式配置
    • 参考链接

原创内容:转载请注明出处

基本概念 - K8S的外部访问方式

NodePort

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第1张图片

尽管通过 NodePort 访问的操作尽管简单,但同时也存在着致命的问题:

  • 每个 Service 只能绑定一个端口;但是本身计算机端口数量有限,存在瓶颈
  • 过多的端口数量不利于维护和管理
  • 为了提高可用性,还需要额外的在外部设置一个负载均衡器
  • Node节点的变更无法及时感知

LoadBalancer

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第2张图片

通过云服务商的 LB 功能也可以达到相应的效果,同时也有一些小问题:

  • 为了保证 LoadBalancer 的稳定性,一般需要依赖于云服务商
  • 云服务商提供的功能付费
  • 每个服务都必须要有一个自己的 IP,不论是内网 IP 或者外网 IP

Ingress

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第3张图片

Ingress 事实上不是一种服务类型,它处于多个服务的前端,扮演着“智能路由”或者集群入口的角色

所以 Ingress 的基本功能包含了以下功能:

  • 提供统一的访问入口
  • 按照路由规则为 Service 提供外部可访问的 URL
  • 负载均衡流量
  • 卸载 SSL/TLS
  • 基于名称的虚拟托管

Ingress 概念

Ingress 简介

Ingress 资源是在 Kubernetes V1.1 中引入的

Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制

举个简单的例子, 我们从对应的 ingress 发送流量到对应的 Service 中

如下图所示:
基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第4张图片

Ingress && ingress-controllers && ingress-class 基本概念

在开始前先描述下 ingress 和 ingress-controllers 的区别:

  • Ingress 是一种资源,不负责接受请求和处理转发,只是一条转发规则而已
  • Ingress-controllers 是一种解释 Ingress 的,并执行的组件; 常见的组件包含
    • ingress-nginx
    • ingress-kong
  • Ingress-class 同一个集群中可以运行多个、多种 ingress-controllers, Ingress-class 则是用来区别这些 控制器 的类型的

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第5张图片

针对 ingress-class 我们再补充几点:

试想一下,当我们的集群中存在多个 ingress-controllers (可以是不同的 控制器类型或者是同一种控制器但全局配置不同); 那么当前配置的这个 ingress 该交给谁来处理和解释呢?

  1. 如果所有的 ingress-controllers 都解析一次 ingress 会造成资源浪费
  2. 针对某些特定的场景,想特别的指定给某个 ingress-controllers

这个时候就可以通过 ingress-class 来区分对应的ingress解析工作交给谁去处理

从 1.14 开始必须要设置对应的 IngressClass, 否则会报错

在 Kubernetes 1.18 版本引入 IngressClass 资源和 ingressClassName 字段之前,Ingress 类是通过 Ingress 中的一个 kubernetes.io/ingress.class 注解来指定的

近期针对 K8S ingress-nginx 的对应版本的变化

Ingress-nginx 原理说明

ingress-controllers 的内部构成

ingress-nginx 源码阅读链接 — 入口函数

Dockerfile 脚本内容

Dockerfile 脚本内容

ingress-controllers 创建成功后,会自动的在集群中创建指定数量的 Pod

此刻我们进入 Pod 中, 查看对应的进程树信息

bash-5.1$ pstree
dumb-init---nginx-ingress-c-+-nginx-+-2*[nginx---32*[{nginx}]]
                            |       `-nginx
                            `-14*[{nginx-ingress-c}]
                            
bash-5.1$ pstree -p
dumb-init(1)---nginx-ingress-c(7)-+-nginx(27)-+-nginx(31)-+-{nginx}(67)
                                  |           |           |-{.....}(68)
                                  |           |           `-{nginx}(98)
                                  |           |-nginx(32)-+-{nginx}(35)
                                  |           |           |-{.....}(36)
                                  |           |           `-{nginx}(66)
                                  |           `-nginx(33)
                                  |-{nginx-ingress-c}(8)
                                  |-{.......}(9)
                                  `-{nginx-ingress-c}(99)
                                  
bash-5.1$ ps 
PID   USER     TIME  COMMAND
    1 www-data  0:00 /usr/bin/dumb-init -- /nginx-ingress-controller --election-id=ingress-controller-leader-nginx --ingress-class=nginx --watch-ingress-without-class --controller-class=k8s.io/ingress-nginx --configmap=kube-system/nginx
    8 www-data  1:36 /nginx-ingress-controller --election-id=ingress-controller-leader-nginx --ingress-class=nginx --watch-ingress-without-class --controller-class=k8s.io/ingress-nginx --configmap=kube-system/nginx-configuration --tcp-s
   27 www-data  0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
  138 www-data  0:58 nginx: worker process
  139 www-data  1:02 nginx: worker process
  140 www-data  0:00 nginx: cache manager process
  259 www-data  0:00 /bin/sh -c TERM=xterm-256color; export TERM; LANG=C.UTF-8; export LANG; [ -x /bin/bash ] && ([ -x /usr/bin/script ] && /usr/bin/script -q -c "/bin/bash" /dev/null || exec /bin/bash) || exec /bin/sh
  265 www-data  0:00 /bin/bash
  266 www-data  0:00 ps          

如果获取不到 PPID 我们可以通过 top 指令来查看

根据上面得到的信息,我们就可以绘制出如下图所示的内容:

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第6张图片

程序通过 dumb-init 启动了 nginx-ingress-controller, nginx-ingress-controller 唤起 nginx

  • nginx-ingress-controller 根据集群中创建的 Ingress 和其他资源动态配置 NGINX
  • nginx 用于实际上负责流量转发功能
    • Master 控制 NGINX 工作进程
    • Worker 处理客户端流量并将流量负载平衡到后端应用程序
    • Cache Manager 应答缓存相关存储和处理

针对nginx的相关进程启动顺序,和运行模式这里不多做介绍

相关资料查阅关键字: master-worker 进程模型

ingress-controllers 的整体运行逻辑

nginx-ingress-c 通过与 api server 交互,监听存储于 etcd 中的 ingress、service、endpoint 等资源的变化

当数据产生变化后则会自动的更新 nginx 对应的配置信息,并 reload nginx 进程; 在此过程中, Pod 和 容器都不会重新创建、重启

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第7张图片

ingress-controllers 内部细化逻辑

ingress-controllers 重载配置逻辑

程序运行后,主程序开启两个协程用于处理同步的数据:

  • Store;用于监听变更并提供查询功能
  • SyncQueue;用于执行 nginx.conf 生产和重启 nginx 服务

Storeapi server 建立连接,监听 ingress 等资源的变化;当变化到达的时候,会写入一个名为 updateChannel 的通道,主程序消费数据并向同步队列追加同步任务(隔离开可以更好的对数据进行缓冲和处理);SyncQueue 定期的拉取处理任务,并向 Store 进行查询对应的 ingress/Service 的详细信息,根据模板生成对应的 nginx.conf 数据;根据变更的数据,判断是否执行重启操作

kubernetes中informer机制基础设计原理

ingress-nginx nginx.conf 模板列表

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第8张图片

客户端通过 Ingress 访问 Pod 的流程

客户端首先通过DNS查找,DNS 返回对应的 IP 地址(前方可能存在防火墙、4层高可用代理); 客户端接着向 Ingress控制器 发送 HTTP 请求,并在 Host 头中指定对应的域名 a.domain.com. 控制器从该头部确认该客户端访问哪个服务,通过与服务关联的 Endpoint 对象查看 Pod IP,并将请求转发给其中一个 Pod

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第9张图片

ingress 资源清单版本差异说明

在 1.9+ 相关的资源配置清单中,会存在差异

我们可以通过相关命令查看对应的资源类型

[root@simple-master-100 ~]# kubectl api-resources|grep ingress
ingresses                         ing          extensions/v1beta1                     true         Ingress
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
ingresses                         ing          networking.k8s.io/v1                   true         Ingress

此处针对 ingress 的资源清单在不同版本中对应的配置不同;重点就在于 后端服务和资源的配置区别 ingresses.spec.rules.http.paths.backend

[root@simple-master-100 ~]# kubectl explain ingresses.spec.rules.http.paths.backend
KIND:     Ingress
VERSION:  networking.k8s.io/v1

RESOURCE: backend 

DESCRIPTION:
     Backend defines the referenced service endpoint to which the traffic will
     be forwarded to.

     IngressBackend describes all endpoints for a given service and port.

FIELDS:
   resource	
     Resource is an ObjectRef to another Kubernetes resource in the namespace of
     the Ingress object. If resource is specified, a service.Name and
     service.Port must not be specified. This is a mutually exclusive setting
     with "Service".

   service	
     Service references a Service as a Backend. This is a mutually exclusive
     setting with "Resource".
 
  

常用注解说明

IngressClass 常用注解

注解名 字段类型 说明
ingressclass.kubernetes.io/is-default-class bool 默认 IngressClass
所有未指定 IngressClass 的 Ingress 都会使用这个 Class 解析

Ingress-nginx 外部认证的注解说明

Ingress-nginx 注解说明 — annotations

注解名 字段类型 说明
kubernetes.io/ingress.class string 多 Ingress controllers 时用于指定ingress制器类型
1.14 + 必须设置
1.18 + 兼容待废弃
v1.22+ 废弃
nginx.ingress.kubernetes.io/auth-url string 外部认证 URL 地址
nginx.ingress.kubernetes.io/auth-method string 外部认证 URL 发起请求的方式
nginx.ingress.kubernetes.io/auth-response-headers string 透传到后端的 Header 头
nginx.ingress.kubernetes.io/enable-cors bool 启用跨域资源共享 (CORS)
nginx.ingress.kubernetes.io/cors-allow-methods string 跨域资源共享 方法
nginx.ingress.kubernetes.io/cors-allow-headers string 跨域资源共享 标头
nginx.ingress.kubernetes.io/cors-allow-origin string 跨域资源共享 源
nginx.ingress.kubernetes.io/configuration-snippet string nginx.conf 下 location 配置片段
nginx.ingress.kubernetes.io/server-snippet string nginx.conf 下 server 配置片段

ingress-nginx 自定义组件开发

ngx_http_lua_module

lua-nginx-module 文档索引

OpenResty 官方网站

引用文档的介绍

ngx_http_lua_module - 将 Lua 的强大功能嵌入到 Nginx HTTP 服务器中(将LuaJIT 2.0/2.1嵌入到 Nginx 中)

该模块是OpenResty的核心组件。如果你在使用这个模块,那么你实际上是在使用 OpenResty

该模块不随 Nginx 源一起分发。请参阅安装说明

这是 OpenResty 的核心组件。如果您使用的是这个模块,那么您实际上是在使用 OpenResty

特殊说明

在原生的 Nginx 中安装这个模块其实是非常麻烦的

如果没有特殊的必要,建议直接使用 OpenResty

openresty 执行指令(过滤器)

openresty 指令说明

lua_need_request_body 参数说明

body_filter_by_lua_block 过滤器说明

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第10张图片

整个请求的生命周期分为四个阶段:

  • 初始化阶段
  • 重定向、访问阶段
  • 内容阶段
  • 日志阶段

在各个阶段中都设置了对应的钩子(过滤器), 我们可以通过在不同的过滤器中设置对应功能块代码来达到相应的处理目的

特别注意:

所有 *_block 的指令都是阻塞 I/O 模式;他们不支持 非阻塞 I/O;
所以我们要尽量避免在代码块中使用一些耗时的处理逻辑

针对不同的阶段,可以使用的函数和变量都不同;
应该注意文档对其变量的说明

ingress-nginx 支持的过滤器

ingress-nginx 自定义插件开发文档说明

ingress-nginx 默认nginx.conf模板

根据手册我们了解到 ingress-nginx 总共支持五种对应的过滤器

  • init_worker: Nginx Worker Process 初始化的时候触发
  • rewrite: 接收到客户端请求体的时候
  • header_filter: 收到后端响应头的时候
  • body_filter: 收到后端应答内容的时候
  • log: 请求全部完成, 进行记录日志的时候

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第11张图片

上图展示了各个阶段的过滤器在一次请求中所处的位置

特别说明的是,init_worker 是在 Nginx 的 Worker进程 初始化的时候工作的,和一次请求的维度不同;所以没有特别的标注出来

那么我们在对应的阶段需要常见的是做些什么呢?

过滤器 常见场景
init_worker 初始化全局常量、变量;做一些全局性的工作
rewrite 修改请求、更改标头、重定向、丢弃请求、进行身份验证等
header_filter 修改响应头
body_filter 修改、记录响应内容,隐私保护等
log 日志记录,日志格式化处理

源码解读

ingress-nginx 插件包源码

ingress-nginx 生成 nginx.conf 默认模板

lua require 函数文档说明

通过上面的描述,我们知道 ingress-nginx-controllers 会默认的根据对应的nginx.conf 模板生成nginx.conf 配置文件

首先查看 nginx.conf 模板 载入插件包的过程和相关代码

// 载入插件扩展包
ok, res = pcall(require, "plugins")
if not ok then
  error("require failed: " .. tostring(res))
else
  plugins = res
end

-- 初始化插件扩展包
-- 遍历配置 $cfg.Plugins 载入插件
plugins.init({ {{ range  $idx, $plugin := $cfg.Plugins }}{{ if $idx }},{{ end }}{{ $plugin | quote }}{{ end }} })

接下来插件包会根据对应的名称查找和载入响应的插件

local function load_plugin(name)
  -- 格式化插件名
  local path = string_format("plugins.%s.main", name)

  -- 按照路径 plugins/{plugins-name}/main.lua 查找文件
  local ok, plugin = pcall(require, path)
  if not ok then
    -- 打印日志
    ngx_log(ERR, string_format("error loading plugin \"%s\": %s", path, plugin))
    return
  end
  
  -- 赋值默认插件名
  local index = #plugins
  if (plugin.name == nil or plugin.name == '') then
    plugin.name = name
  end
  
  -- 写入变量
  plugins[index + 1] = plugin
end

在 nginx.conf 模板 调用处查看到,这里调用了 plugins.run() 方法

header_filter_by_lua_block {
    lua_ingress.header()
    plugins.run()
}

看看插件包是如何循环遍历的

function _M.run()

  -- 获取当前执行阶段
  local phase = ngx.get_phase()

  -- 顺序遍历插件集合
  for _, plugin in ipairs(plugins) do
    -- 存在指定阶段函数
    if plugin[phase] then
      ngx_log(INFO, string_format("running plugin \"%s\" in phase \"%s\"", plugin.name, phase))

      -- 执行调用成员函数
      local ok, err = pcall(plugin[phase])
      if not ok then
        ngx_log(ERR, string_format("error while running plugin \"%s\" in phase \"%s\": %s",
            plugin.name, phase, err))
      end
    end
  end
end

从上面的源码分析中我们得知几个关于插件相关的重要信息:

  • 文件夹名称就是插件名称
  • 插件的加载顺序可以通过配置自定义
  • 插件的执行顺序为先进先出 (按照配置plugins加载顺序执行)
  • 插件对象的成员函数名需要和指定的阶段一一对应

扩展组件

为了避免概念混淆,我们做如下约定

  • ingress-nginx 的自定义 plugins,我们称为 扩展组件
  • 而在 lua 中引用的一些工具包,我们称为 第三方库

扩展组件 && 第三方库 存放位置

Nginx 参数lua_package_path - Lua 模块搜索路径

Nginx 参数lua_package_cpath - Lua C 模块搜索路径

lua require 功能加载路径 - LUA_PATH

在 nginx.conf 模板 我们观察到,这里设置了 lua_package_path 默认的搜寻路径

http {
    lua_package_path "/etc/nginx/lua/?.lua;;";
}
stream {
    lua_package_path "/etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/?.lua;;";
}

所以对应的 扩展组件 可以存放在指定目录下:/etc/nginx/lua/?.lua

注意

同时为了更加符合标准,建议将 扩展组件 存放在 /etc/nginx/lua/plugins 的子目录中

第三方库,为了让全局都能使用,建议存放在 lua 的默认搜索 路径下;我们通过查看环境变量 LUA_PATH 确认对应目录

bash-5.1$ export |grep LUA
declare -x LUA_CPATH="/usr/local/lib/lua/?/?.so;/usr/local/lib/lua/?.so;;"
declare -x LUA_PATH="/usr/local/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/lib/lua/?.lua;;"

所以对应的 第三方库 可以存放在指定目录下:/usr/local/lib/lua/?.lua

注意

为了结构更加清晰,建议新建一个对应的目录用于存放一些网络上的第三方库

内部使用子目录进行包裹,可以更加的清晰

举个例子,需要使用到 扩展组件 unique-id, 第三方库 snowflake 两个模块, 那么我们的目录就可以这样组织

    /etc/nginx/lua/plugins
        |
        |-- unique-id [定义目录,满足加载规则]
        |     |
        |     |-- main.lua [扩展组件]
        
    /usr/local/lib/lua
        |
        |-- github [随便自定义一个,作为存放第三方库的目录]
        |     |
        |     |-- snowflake [存放在子目录会更加清晰]
        |     |     |
        |     |     |-- snowflake.lua [第三方库]

扩展组件 编写

ingress-nginx 扩展组件示例 - openidc

openresty lua-nginx-module 文档 - 扩展组件文档手册

正常情况下,我们可以按照如下格式来编写 扩展组件

local _M = {}

-- 按照指定阶段和对应的函数名一一对应
function _M.rewrite()
        -- 业务代码
        ...
end

return _M

比如,官方的默认扩展组件 helloWord、openidc

内部相关的语法和注意事项可以查阅 手册文档

安装扩展组件 (构建镜像)

从前面我们知道,如果有使用到第三方组件的时候,就需要将目录拷贝到 /usr/local/lib/lua/ 底下;而自定义的 扩展组件直接拷贝到 /etc/nginx/lua/plugins 底下就行了

这就是 安装插件 的全部过程了:拷贝代码到指定目录

由于,我们使用的是K8S环境,所以我们需要在本地打包一下对应的本地镜像,然后推送到远端

下方为示例的 Dockerfile

# 应用 ACK 对应的镜像文件
FROM registry.cn-hangzhou.aliyuncs.com/acs/aliyun-ingress-controller:v1.1.0-aliyun.2

# 指定工作目录到 nginx lua 脚本目录中
WORKDIR /etc/nginx/lua

# 在组件目录下创建指定的文件夹
RUN mkdir plugins/unique-id

# 拷贝第三方库到插件目录
COPY ./lib/resty/ /usr/local/lib/lua/resty/

# 拷贝扩展组件到组件目录
COPY ./unique-id ./plugins/unique-id

本项目使用的基础镜像为 ASK 自研的镜像文件 registry.cn-hangzhou.aliyuncs.com/acs/aliyun-ingress-controller:v1.1.0-aliyun.2

如果需要本地使用, 则需要更换对应的基础镜像

需要注意的是,基础镜像必须为 ingress-nginx 的镜像

启用扩展组件

光安装完毕,还没有启用;所以需要在指定的 ConfigMap 中添加指定的扩展组件名来启用这个组件

ingress-nginxconfigmap 中添加指定的配置项 plugins

hello_word, ....., unique-id

此时 IC 就会自动的重载 Nginx 的配置

项目实战

项目概述

使用部署工具安装 Kubernetes

ingress-nginx 官方部署文档 — Installation Guide

借助 阿里云 旗下相关服务 实现如下规划的项目

  • ASK(K8S 集群)
  • SLS(日志收集传输服务)
  • ADB(存储分析数据库)

使用 ingress-nginx 作为唯一网关入口;

在 access 阶段,使用外部认证,解析指定 header 头认证密钥;在外部进行校验,并将数据设置到对应的 Header 头中传递给后端服务;

由于后端服务处于初期改造迁移阶段,不考虑做认证不通过的拦截;在相关位置会做备注说明

接下来根据 path 将请求分流到不同的 后端服务

path pathType Backend
/v3 Prefix api-go-v3
/ Prefix api-php-v1

在访问日志中增加响应的内容的数据,然后将访问日志接入到分析型数据库中进行存储

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第12张图片

接下来会详细说明对应不同功能模块的作用和区别

外部认证原理

使用外部 auth-service 对请求连接进行认证

使用外部 auth-service 对请求连接进行认证 – 译文

Nginx 执行阶段顺序

Nginx 外部认证模块文档

请认证观看完上方文章后,再阅读下方文章

Nginx 在处理外部认证使用了 ngx_http_auth_request_module 模块

ingress 控制器通过注解,生成对应的配置信息;在 location 处根据配置生成了对应的代理转发配置

Nginx 在处理连接时,按照对应流程,在 Access 处的 Auth_request 流程对其进行请求转发和判断

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第13张图片

ingress-nginx 会根据对应的配置生成如下配置信息

完整配置如下:

location = /_external-auth-L3YzLw-Prefix {
        internal;

        # ngx_auth_request module overrides variables in the parent request,
        # therefore we have to explicitly set this variable again so that when the parent request
        # resumes it has the correct value set for this variable so that Lua can pick backend correctly
        set $proxy_upstream_name "default-api-go-v3-80";

        proxy_pass_request_body     off;
        proxy_set_header            Content-Length          "";
        proxy_set_header            X-Forwarded-Proto       "";
        proxy_set_header            X-Request-ID            $req_id;

        proxy_method                GET;
        proxy_set_header            X-Original-URI          $request_uri;
        proxy_set_header            X-Scheme                $pass_access_scheme;

        proxy_set_header            Host                    auth-token-resolve-v1.default.svc.cluster.local;
        proxy_set_header            X-Original-URL          $scheme://$http_host$request_uri;
        proxy_set_header            X-Original-Method       $request_method;
        proxy_set_header            X-Sent-From             "nginx-ingress-controller";
        proxy_set_header            X-Real-IP               $remote_addr;

        proxy_set_header            X-Forwarded-For        $remote_addr;

        proxy_set_header            X-Auth-Request-Redirect $request_uri;

        proxy_buffering                         off;

        proxy_buffer_size                       4k;
        proxy_buffers                           4 4k;
        proxy_request_buffering                 on;
        proxy_http_version                      1.1;

        proxy_ssl_server_name       on;
        proxy_pass_request_headers  on;
                                
        client_max_body_size        20m;

        # Pass the extracted client certificate to the auth provider

        set $target http://auth-token-resolve-v1.default.svc.cluster.local/authenticate;
        proxy_pass $target;
}

location /v3/ {
        
        .....
        
        # this location requires authentication
        auth_request        /_external-auth-L3YzLw-Prefix;
        auth_request_set    $auth_cookie $upstream_http_set_cookie;
        add_header          Set-Cookie $auth_cookie;
        
        # 将解析好的 UserId 写入对应的转发头中
        auth_request_set $authHeader0 $upstream_http_x_user_id;
        proxy_set_header 'X-User-Id' $authHeader0;
        
        # 这里是模拟了附加信息
        auth_request_set $authHeader1 $upstream_http_x_user_attr;
        proxy_set_header 'X-User-attr' $authHeader1;
        
        ....
}

(1) WAF 防火墙

DNS A记录 同步解析到防火墙中,同时负责对证书进行卸载;

相关内容可以参阅 阿里云WAF 相关文档;(本服务属于第三方服务,所以不做太多解释)

WAF 产品文档手册

WAF 域名接入

(2) api-go-v3

本次使用到的镜像,请移步 4.9-2 kubernetes - 自定义全局认证案例 - api-go-v3 准备说明 查看详细的内容

本次的请求链路和端口如下所示

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第14张图片

# -------------------------------------------------------
# api-go-v3 的 SVC
# -------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: api-go-v3       # 指定对应的 SVC 名称
  namespace: default    # SVC 对应的命名空间
spec:
  selector:             # 对应的 Pod 选择器
    app: api-go-v3      
    release: devtest
  ports:
  - name: http
    targetPort: 9999    # 目标的 Pod 对接端口
    port: 80            # 当前 SVC 开放的端口
---
# -------------------------------------------------------
# api-go-v3 的 控制器
# -------------------------------------------------------
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-go-v3-pod
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api-go-v3
      release: devtest
  template:
    metadata:
      labels:
        app: api-go-v3
        release: devtest
    spec:
      containers:
      - name: web
        image: registry.cn-hangzhou.aliyuncs.com/7799520/test-2022040710-api-go-v3:v1 # 容器镜像
        ports:
        - name: http
          containerPort: 9999  # 容器端口

小贴士:
如果 SVC设置了标签选择器,K8S 会自动的创建 Endpoint

(3) api-php-v1

本次使用到的镜像,为默认的nginx镜像

本次的请求链路和端口如下所示

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第15张图片

# -------------------------------------------------------
# api-php-v1 的 SVC
# -------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: api-php-v1      # 指定对应的 SVC 名称
  namespace: default    # SVC 对应的命名空间
spec:
  selector:             # 对应的 Pod 选择器
    app: api-php-v1
    release: devtest
  ports:
  - name: http
    targetPort: 80      # 目标的 Pod 对接端口
    port: 80            # 当前 SVC 开放的端口
---
# -------------------------------------------------------
# api-php-v1 的 控制器
# -------------------------------------------------------
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-php-v1-pod
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api-php-v1
      release: devtest
  template:
    metadata:
      labels:
        app: api-php-v1
        release: devtest
    spec:
      containers:
      - name: web
        image: nginx:1.20
        ports:
        - name: http
          containerPort: 80

(4) 外部缓存存储

我们可以通过 为外部的存储服务 提供一个统一的服务名; 如果 我们需要对外部服务进行地址更改等操作,只需要对该 SVC 进行变更即可,无需对所有的服务进行重启和修改

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第16张图片

以下两种模式,根据需要自行选择一种模式即可

使用 URI 模式进行连接的服务,可以使用 ExternalName 进行别名映射

# -------------------------------------------------------
# 第三方服务, URI 模式连接的外服服务
# -------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: token-cache            # SVC 名称
  namespace: default
spec:
  type: ExternalName
  externalName: r-bp108c1255527b14pd.redis.rds.aliyuncs.com # 外部 redis 访问地址
  ports:
  - name: access
    port: 6379                 # 内部访问端口
    protocol: TCP
    targetPort: 6379           # 外部 redis 访问端口

使用 IP 模式进行连接的服务,可以使用自定义Endpoints进行端口映射

# -------------------------------------------------------
# 自己类服务, IP 模式连接的外服服务
# -------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: token-cache            # SVC 名称
  namespace: default
spec:
  ports:
  - name: token-cache
    port: 6379                 # 服务内部使用端口
    protocol: TCP
    targetPort: 31079          # endpoint 流量端口
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  name: token-cache
  namespace: default
subsets:
- addresses:
  - ip: 47.103.129.166         # 外部服务 IP 地址
  ports:
  - name: token-cache
    port: 31079                # 外部服务 端口地址
    protocol: TCP

(5) 外部认证服务

本次使用到的镜像,请移步 4.9-2 kubernetes - 自定义全局认证案例 - auth-token-resolve-v1 准备说明 查看详细的内容

本次的请求链路和端口如下所示

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第17张图片

apiVersion: v1
kind: Service
metadata:
  name: auth-token-resolve-v1
  namespace: default
spec:
  selector:
    app: auth-token-resolve-v1
    release: devtest
  ports:
  - name: http
    targetPort: 9999    # 目标的 Pod 对接端口
    port: 80            # 当前 SVC 开放的端口
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-token-resolve-v1-pod
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: auth-token-resolve-v1
      release: devtest
  template:
    metadata:
      labels:
        app: auth-token-resolve-v1
        release: devtest
    spec:
      containers:
      - name: web
        image: registry.cn-hangzhou.aliyuncs.com/7799520/test-2022040710-auth-token-resolve-v1:v1 # 认证服务镜像
        ports:
        - name: http
          containerPort: 9999

(6) Ingress 控制器

本次技术选型,直接采用的 Ingress-nginx 作为 Ingress控制器

由于测试环境的K8S版本为 v1.20, 生产环境版本为v1.22;且v1.22之前针对Ingress存在较大改动,废弃了对应 IngressClass 的注解,所以在部署上有区别;本文以下内容默认为最新版本的 v1.22 相关文档,默认不讨论 v1.20 的资源清单编写;

详细内容可以查询命令: kubectl explain ingresses

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第18张图片

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-api.7799520     # Ingress 对应名称
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/auth-url: http://auth-token-resolve-v1.default.svc.cluster.local/authenticate
    nginx.ingress.kubernetes.io/auth-response-headers: X-User-Id, X-User-attr
    nginx.ingress.kubernetes.io/auth-method: GET
    nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
  ingressClassName: nginx       # 1.22+ 必须要设置 IngressClass
  rules:                        
  - host: api.7799520.com       # 对应的域名
    http:
      paths:
      - pathType: Prefix        # 匹配模式采用前缀匹配
        path: /v3               # 匹配的路径
        backend:                # 后端服务集合
          service:
            name: api-go-v3     # SVC 名称
            port:
              number: 80        # SVC 端口
      - pathType: Prefix
        path: /
        backend:
          service:
            name: api-php-v1
            port:
              number: 80

测试流程

在仔细阅读上面的步骤之后,我们执行各个yaml的运行操作,之后准备进入测试了

检查相关 Pod 的运行状态

bash-5.1# kubectl get pods
NAME                                         READY   STATUS    RESTARTS   AGE
api-go-v3-pod-5d8b4cb775-fsfgq               1/1     Running   0          24h
api-php-v1-pod-6fb98dd485-q7kw9              1/1     Running   0          24h
auth-token-resolve-v1-pod-67cbf79d5c-cndvr   1/1     Running   0          19h

当看到我们的三个Pod的状态都是 Running 的时候,表示三个 Pod 所有 Pod 都在运行了

检查 SVC 的启动状态

bash-5.1# kubectl get svc
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
api-go-v3               ClusterIP   172.21.0.7              80/TCP     24h
api-php-v1              ClusterIP   172.21.4.255            80/TCP     24h
auth-token-resolve-v1   ClusterIP   172.21.15.154           80/TCP     23h
token-cache             ClusterIP   172.21.2.126            6379/TCP   19h

所有 SVC 都创建成功了,接下来分别测试各自的连通性

api-go-v3
bash-5.1# curl -Lv0 api-go-v3.default.svc.cluster.local
*   Trying 172.21.0.7:80...
* Connected to api-go-v3.default.svc.cluster.local (172.21.0.7) port 80 (#0)
> GET / HTTP/1.0
> Host: api-go-v3.default.svc.cluster.local
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Fri, 08 Apr 2022 03:56:24 GMT
< Content-Length: 17
< Content-Type: text/plain; charset=utf-8
< 
* Closing connection 0
Other!!!!!!!!!!!!
api-php-v1
bash-5.1# curl -Lv0 api-php-v1.default.svc.cluster.local
*   Trying 172.21.4.255:80...
* Connected to api-php-v1.default.svc.cluster.local (172.21.4.255) port 80 (#0)
> GET / HTTP/1.0
> Host: api-php-v1.default.svc.cluster.local
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.20.2
< Date: Fri, 08 Apr 2022 03:57:44 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Tue, 16 Nov 2021 14:44:02 GMT
< Connection: close
< ETag: "6193c3b2-264"
< Accept-Ranges: bytes
< 


........

Thank you for using nginx.

........ * Closing connection 0
auth-token-resolve-v1

此处有点不同,因为请求的认证 URL 是确定的,所以我们应该检查 Header 头是否能够正常追加信息

bash-5.1# curl -Lv0 auth-token-resolve-v1.default.svc.cluster.local/authenticate
*   Trying 172.21.15.154:80...
* Connected to auth-token-resolve-v1.default.svc.cluster.local (172.21.15.154) port 80 (#0)
> GET /authenticate HTTP/1.0
> Host: auth-token-resolve-v1.default.svc.cluster.local
> User-Agent: curl/7.79.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< X-User-Attr: 中文附加信息测试
< X-User-Id: 
< Date: Fri, 08 Apr 2022 03:59:01 GMT
< Content-Length: 2
< Content-Type: text/plain; charset=utf-8
< 
* Closing connection 0
token-cache

此处需要使用 redis 客户端进行连接测试连通性

bash-5.1# redis-cli -h token-cache.default.svc.cluster.local -p 6379 -a 2E97fc51
r-bp108c1255527b14.redis.rds.aliyuncs.com:6379> 

整体测试

打开 api-go-v3-pod 观察对应的日志信息

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第19张图片

打开 auth-token-resolve-v1-pod 观察对应的日志信息

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第20张图片

查看对应 ingress 控制器的端点

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第21张图片

接下来我们使用 POSTMAN 来测试具体的请求

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第22张图片

切换请求路径可以看到内容变更,路由更能被正常处理了

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第23张图片

接下来我们检查token是否能被校验出来; (token 被设置为 “aaaaaa”, 所以 redis 键应该为 “test_token_aaaaaa”)

先发送一次请求看一下是否存在 Token 解析后的 X-User-Id

检查 api-go-v3-pod 日志信息,没有发现 X-User-Id, 但附加信息追加了 X-User-Attr

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第24张图片

此时在 redis 中增加了对应的 KEY test_token_aaaaaa 内容

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第25张图片

再次请求,观察 api-go-v3-pod 日志, 两个参数的信息都带上了 X-User-IdX-User-Attr

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第26张图片

阿里云 - ACK下Ingress-nginx的日志收集

整体流程描述

在 ingress-nginx 中,对应的增加了一些相关的配置信息,这些配置信息默认是放在 ConfigMap 中的 nginx-configuration

nginx-ingress-controller 根据设置的配置的日志格式,生成 nginx.conf 配置

输出到 access_log, err_log 后, 通过 logstash 收集到 sls 日志收集平台

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第27张图片

日志格式配置

基于阿里云的 ingress-nginx 外部认证实战【部分未完成】_第28张图片

参考链接

k8s 相关 ingress 文档

ingress-nginx 官方网站

ingress-nginx Github 源码

ingress-nginx 外部认证例子

ingress-nginx 注解说明

ingress 官网说明

ingress-controllers 官网说明

ingress-nginx 参数调优相关文章

Kubernetes V1.22版本升级说明

V1.22 API 版本说明

Nginx 官方 针对 ingress-nginx 的介绍说明文档

kubernetes中informer机制基础设计原理

ingress-nginx nginx.conf 模板列表

WAF 产品文档手册

ingress-nginx 全部注解解析源码

ingress-nginx auth-url 注解解析源码

ingress-nginx store协程源码位置

Nginx 外部认证模块文档

Nginx 执行阶段顺序

使用外部 auth-service 对请求连接进行认证

使用外部 auth-service 对请求连接进行认证 – 译文

Nginx-ingress 官方文档针对插件开发的说明

openresty lua-nginx-module 使用手册

Nginx respon body filters – 应答过滤器说明

Ingress-Nginx body_filter_by_lua_block lua 插件添加到 ingress-nginx – 修复说明

Ingress-Nginx 默认 log 阶段过滤器模板 – line:1160

你可能感兴趣的:(nginx,阿里云,运维,微服务,网络)