• 泛域名转发
    • 需求
    • 简单做法
    • 正确姿势

    泛域名转发

    需求

    集群对外暴露了一个公网IP作为流量入口(可以是 Ingress 或 Service),DNS 解析配置了一个泛域名指向该IP(比如 *.test.imroc.io),现希望根据请求中不同 Host 转发到不同的后端 Service。比如 a.test.imroc.io 的请求被转发到 my-svc-ab.test.imroc.io 的请求转发到 my-svc-b。当前 K8S 的 Ingress 并不原生支持这种泛域名转发规则,本文将给出一个解决方案来实现泛域名转发。

    简单做法

    先说一种简单的方法,这也是大多数人的第一反应:配置 Ingress 规则

    假如泛域名有两个不同 Host 分别转发到不同 Service,Ingress 类似这样写:

    1. apiVersion: extensions/v1beta1
    2. kind: Ingress
    3. metadata:
    4. name: my-ingress
    5. spec:
    6. rules:
    7. - host: a.test.imroc.io
    8. http:
    9. paths:
    10. - backend:
    11. serviceName: my-svc-a
    12. servicePort: 80
    13. path: /
    14. - host: b.test.imroc.io
    15. http:
    16. paths:
    17. - backend:
    18. serviceName: my-svc-b
    19. servicePort: 80
    20. path: /

    但是!如果 Host 非常多会怎样?(比如200+)

    • 每次新增 Host 都要改 Ingress 规则,太麻烦
    • 单个 Ingress 上面的规则越来越多,更改规则对 LB 的压力变大,可能会导致偶尔访问不了

    正确姿势

    我们可以约定请求中泛域名 Host 通配符的 * 号匹配到的字符跟 Service 的名字相关联(可以是相等,或者 Service 统一在前面加个前缀,比如 a.test.imroc.io 转发到 my-svc-a 这个 Service),集群内起一个反向代理服务,匹配泛域名的请求全部转发到这个代理服务上,这个代理服务只做一件简单的事,解析 Host,正则匹配抓取泛域名中 * 号这部分,把它转换为 Service 名字,然后在集群里转发(集群 DNS 解析)

    这个反向代理服务可以是 Nginx+Lua脚本 来实现,或者自己写个简单程序来做反向代理,这里我用 OpenResty 来实现,它可以看成是 Nginx 的发行版,自带 lua 支持。

    有几点需要说明下:

    • 我们使用 nginx 的 proxy_pass 来反向代理到后端服务,proxy_pass 后面跟的变量,我们需要用 lua 来判断 Host 修改变量
    • nginx 的 proxy_pass 后面跟的如果是可变的域名(非IP,需要 dns 解析),它需要一个域名解析器,不会走默认的 dns 解析,需要在 nginx.conf 里添加 resolver 配置项来设置一个外部的 dns 解析器
    • 这个解析器我们是用 go-dnsmasq 来实现,它可以将集群的 dns 解析代理给 nginx,以 sidecar 的形式注入到 pod 中,监听 53 端口

    nginx.conf 里关键的配置如下图所示:

    nginx.conf

    下面给出完整的 yaml 示例

    proxy.yaml:

    1. apiVersion: apps/v1beta1
    2. kind: Deployment
    3. metadata:
    4. labels:
    5. component: nginx
    6. name: proxy
    7. spec:
    8. replicas: 1
    9. selector:
    10. matchLabels:
    11. component: nginx
    12. template:
    13. metadata:
    14. labels:
    15. component: nginx
    16. spec:
    17. containers:
    18. - name: nginx
    19. image: "openresty/openresty:centos"
    20. ports:
    21. - name: http
    22. containerPort: 80
    23. protocol: TCP
    24. volumeMounts:
    25. - mountPath: /usr/local/openresty/nginx/conf/nginx.conf
    26. name: config
    27. subPath: nginx.conf
    28. - name: dnsmasq
    29. image: "janeczku/go-dnsmasq:release-1.0.7"
    30. args:
    31. - --listen
    32. - "127.0.0.1:53"
    33. - --default-resolver
    34. - --append-search-domains
    35. - --hostsfile=/etc/hosts
    36. - --verbose
    37. volumes:
    38. - name: config
    39. configMap:
    40. name: configmap-nginx
    41. ---
    42. apiVersion: v1
    43. kind: ConfigMap
    44. metadata:
    45. labels:
    46. component: nginx
    47. name: configmap-nginx
    48. data:
    49. nginx.conf: |-
    50. worker_processes 1;
    51. error_log /error.log;
    52. events {
    53. accept_mutex on;
    54. multi_accept on;
    55. use epoll;
    56. worker_connections 1024;
    57. }
    58. http {
    59. include mime.types;
    60. default_type application/octet-stream;
    61. log_format main '$time_local $remote_user $remote_addr $host $request_uri $request_method $http_cookie '
    62. '$status $body_bytes_sent "$http_referer" '
    63. '"$http_user_agent" "$http_x_forwarded_for" '
    64. '$request_time $upstream_response_time "$upstream_cache_status"';
    65. log_format browser '$time_iso8601 $cookie_km_uid $remote_addr $host $request_uri $request_method '
    66. '$status $body_bytes_sent "$http_referer" '
    67. '"$http_user_agent" "$http_x_forwarded_for" '
    68. '$request_time $upstream_response_time "$upstream_cache_status" $http_x_requested_with $http_x_real_ip $upstream_addr $request_body';
    69. log_format client '{"@timestamp":"$time_iso8601",'
    70. '"time_local":"$time_local",'
    71. '"remote_user":"$remote_user",'
    72. '"http_x_forwarded_for":"$http_x_forwarded_for",'
    73. '"host":"$server_addr",'
    74. '"remote_addr":"$remote_addr",'
    75. '"http_x_real_ip":"$http_x_real_ip",'
    76. '"body_bytes_sent":$body_bytes_sent,'
    77. '"request_time":$request_time,'
    78. '"status":$status,'
    79. '"upstream_response_time":"$upstream_response_time",'
    80. '"upstream_response_status":"$upstream_status",'
    81. '"request":"$request",'
    82. '"http_referer":"$http_referer",'
    83. '"http_user_agent":"$http_user_agent"}';
    84. access_log /access.log main;
    85. sendfile on;
    86. keepalive_timeout 120s 100s;
    87. keepalive_requests 500;
    88. send_timeout 60000s;
    89. client_header_buffer_size 4k;
    90. proxy_ignore_client_abort on;
    91. proxy_buffers 16 32k;
    92. proxy_buffer_size 64k;
    93. proxy_busy_buffers_size 64k;
    94. proxy_send_timeout 60000;
    95. proxy_read_timeout 60000;
    96. proxy_connect_timeout 60000;
    97. proxy_cache_valid 200 304 2h;
    98. proxy_cache_valid 500 404 2s;
    99. proxy_cache_key $host$request_uri$cookie_user;
    100. proxy_cache_methods GET HEAD POST;
    101. proxy_redirect off;
    102. proxy_http_version 1.1;
    103. proxy_set_header Host $http_host;
    104. proxy_set_header X-Real-IP $remote_addr;
    105. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    106. proxy_set_header X-Forwarded-Proto $scheme;
    107. proxy_set_header X-Frame-Options SAMEORIGIN;
    108. server_tokens off;
    109. client_max_body_size 50G;
    110. add_header X-Cache $upstream_cache_status;
    111. autoindex off;
    112. resolver 127.0.0.1:53 ipv6=off;
    113. server {
    114. listen 80;
    115. location / {
    116. set $service '';
    117. rewrite_by_lua '
    118. local host = ngx.var.host
    119. local m = ngx.re.match(host, "(.+).test.imroc.io")
    120. if m then
    121. ngx.var.service = "my-svc-" .. m[1]
    122. end
    123. ';
    124. proxy_pass http://$service;
    125. }
    126. }
    127. }

    让该代理服务暴露公网访问可以用 Service 或 Ingress

    用 Service 的示例 (service.yaml):

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. labels:
    5. component: nginx
    6. name: service-nginx
    7. spec:
    8. type: LoadBalancer
    9. ports:
    10. - name: http
    11. port: 80
    12. targetPort: http
    13. selector:
    14. component: nginx

    用 Ingress 的示例 (ingress.yaml):

    1. apiVersion: extensions/v1beta1
    2. kind: Ingress
    3. metadata:
    4. name: ingress-nginx
    5. spec:
    6. rules:
    7. - host: "*.test.imroc.io"
    8. http:
    9. paths:
    10. - backend:
    11. serviceName: service-nginx
    12. servicePort: 80
    13. path: /