当前位置:   article > 正文

APISIX运维优化之配置文件自动化生成方案_apisix 频繁更新配置

apisix 频繁更新配置

在这个容器化技术盛行的时代,大家都习惯采用 Docker 或者 K8S 来运行 APISIXAPISIX 的配置参数非常多,因此很多介绍文章都采用挂载文件或者 K8S Configmap 的方式来配置 APISIX。最开始我们就采用 Configmap 的方式在腾讯云 TKE 上部署 APISIX,当网络区域越开越多时,每个 TKE 集群都需要去定义一套 config.yaml 对应的 Configmap,管理非常繁琐。因此,这里我们利用 Python 的 Jinja2 插件来自动化渲染 APISIX 的配置文件,整体非常方便!

一、Jinja2 模板

熟悉 Jinja2 的同学都很清楚,要通过 Jinja2 生成所需文件,需要先定制一个渲染模板,Jinja2 的原理就是将动态的内容填充到模板中,最终渲染成所需文件。因此,这里参考 APISIX 官方最新 2.10.0 版本config-default.yaml配置文件制作了 Jinja2 的配置模板如下:

  1. apisix:
  2. node_listen:
  3. - ip: {{ http_listen_ip | default("0.0.0.0") }}
  4. port: {{ http_listen_port | default(9080) | int }}
  5. enable_http2: {{ http_enable_http2 | default("false") }}
  6. {% if multi_http_ports: -%}
  7. # supports more listen ports
  8. {% for port in multi_http_ports | regex_split -%}
  9. {% if port: -%}
  10. - {{port}}
  11. {% endif -%}
  12. {% endfor -%}
  13. {% endif %}
  14. enable_admin: {{ enable_admin | default("true") }}
  15. enable_admin_cors: {{ enable_admin_cors | default("true") }}
  16. enable_dev_mode: {{ enable_dev_mode | default("false") }}
  17. enable_reuseport: {{ enable_reuseport | default("true") }}
  18. enable_ipv6: {{ enable_ipv6 | default("false") }}
  19. config_center: {{ config_center | default("etcd") }}
  20. enable_server_tokens: {{ enable_server_tokens | default("true") }}
  21. extra_lua_path: {{ extra_lua_path | default("") }}
  22. extra_lua_cpath: {{ extra_lua_cpath | default("") }}
  23. proxy_cache:
  24. cache_ttl: {{ cache_ttl | default("3600s") }}
  25. zones:
  26. - name: {{ proxy_cache_zones | default("disk_cache_one") }}
  27. memory_size: {{ proxy_cache_memory_size | default("50m") }}
  28. disk_size: {{ proxy_cache_disk_size | default("1G") }}
  29. disk_path: {{ proxy_cache_disk_path | default("/tmp/disk_cache_one") }}
  30. cache_levels: {{ proxy_cache_cache_levels | default("1:2") }}
  31. allow_admin:
  32. {% if allow_admin_subnet: -%}
  33. {%- for item in allow_admin_subnet | regex_split -%}
  34. {% if item: -%}
  35. - {{item}}
  36. {% endif -%}
  37. {% endfor %}
  38. {%- endif -%}
  39. admin_key:
  40. -
  41. name: {{ admin_key_name | default("admin") }}
  42. key: {{ admin_key_secret | default("d208uj44fnd2yk6quczd6szkytvoi0x1") }}
  43. role: admin
  44. -
  45. name: {{ viewer_key_name | default("viewer") }}
  46. key: {{ viewer_key_secret | default("4054f7cf07e344346cd3f287985e76a2") }}
  47. role: viewer
  48. delete_uri_tail_slash: {{ delete_uri_tail_slash | default("false") }}
  49. global_rule_skip_internal_api: {{ global_rule_skip_internal_api | default("true") }}
  50. router:
  51. http: {{ router_http | default("radixtree_uri") }}
  52. ssl: {{ router_ssl | default("radixtree_sni") }}
  53. resolver_timeout: {{ resolver_timeout | default(3) | int }}
  54. enable_resolv_search_opt: {{ enable_resolv_search_opt | default("true") }}
  55. ssl:
  56. enable: {{ ssl_enable | default("true") }}
  57. enable_http2: {{ ssl_enable_http2 | default("true") }}
  58. listen:
  59. - ip: {{ https_listen_ip | default("0.0.0.0") }}
  60. port: {{ https_listen_port | default(9443) | int }}
  61. enable_http2: {{ https_enable_http2 | default("true") }}
  62. {% if multi_https_ports: -%}
  63. # supports more listen ports
  64. {% for port in multi_https_ports | regex_split -%}
  65. {% if port: -%}
  66. - {{port}}
  67. {% endif -%}
  68. {% endfor -%}
  69. {% endif %}
  70. ssl_protocols: {{ ssl_protocols | default("TLSv1 TLsV1.1 TLSv1.2 TLSv1.3") }}
  71. ssl_ciphers: {{ ssl_ciphers | default("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384") }}
  72. ssl_session_tickets: {{ ssl_session_tickets | default("true") }}
  73. key_encrypt_salt: {{ key_encrypt_salt | default("edd1c9f0985e76a2") }}
  74. enable_control: {{ enable_control | default("true") }}
  75. control:
  76. ip: {{ control_ip | default("127.0.0.1") }}
  77. port: {{ control_port | default(9090) | int }}
  78. disable_sync_configuration_during_start: {{ disable_sync_configuration_during_start | default("false") }}
  79. nginx_config:
  80. user: {{ nginx_user | default("root") }}
  81. error_log: {{ error_log | default("/dev/stdout") }}
  82. error_log_level: {{ error_log_level | default("warn") }}
  83. worker_processes: {{ worker_processes | default(1) | int }}
  84. enable_cpu_affinity: {{ enable_cpu_affinity | default("true") }}
  85. worker_rlimit_nofile: {{ worker_rlimit_nofile | default(20480) | int }}
  86. worker_shutdown_timeout: {{ worker_shutdown_timeout | default("240s") }}
  87. event:
  88. worker_connections: {{ worker_connections | default(20480) | int }}
  89. envs:
  90. {% if not nginx_config_env: -%}
  91. {%- set nginx_config_env = "POD_IP" -%}
  92. {%- endif -%}
  93. {%- for item in nginx_config_env | regex_split -%}
  94. {% if item: -%}
  95. - {{item}}
  96. {% endif -%}
  97. {% endfor %}
  98. stream:
  99. lua_shared_dict:
  100. etcd-cluster-health-check-stream: 10m
  101. lrucache-lock-stream: 10m
  102. plugin-limit-conn-stream: 10m
  103. {% if not stream_lua_shared_dicts: -%}
  104. {%- set stream_lua_shared_dicts = "" -%}
  105. {%- endif -%}
  106. {%- for item in stream_lua_shared_dicts | regex_split -%}
  107. {% if item: -%}
  108. {{item}}
  109. {%- endif -%}
  110. {% endfor %}
  111. main_configuration_snippet: |
  112. {% if not main_configuration_snippet: -%}
  113. {%- set main_configuration_snippet = "" -%}
  114. {%- endif -%}
  115. {%- for item in main_configuration_snippet | regex_split -%}
  116. {% if item: -%}
  117. {{item}}
  118. {% endif -%}
  119. {% endfor %}
  120. http_configuration_snippet: |
  121. {% if not http_configuration_snippet: -%}
  122. {%- set http_configuration_snippet = "" -%}
  123. {%- endif -%}
  124. {%- for item in http_configuration_snippet | regex_split -%}
  125. {% if item: -%}
  126. {{item}}
  127. {% endif -%}
  128. {% endfor %}
  129. http_server_configuration_snippet: |
  130. {% if not http_server_configuration_snippet: -%}
  131. {%- set http_server_configuration_snippet = "" -%}
  132. {%- endif -%}
  133. {%- for item in http_server_configuration_snippet | regex_split -%}
  134. {% if item: -%}
  135. {{item}}
  136. {% endif -%}
  137. {% endfor %}
  138. http_admin_configuration_snippet: |
  139. {% if not http_admin_configuration_snippet: -%}
  140. {%- set http_admin_configuration_snippet = "" -%}
  141. {%- endif -%}
  142. {%- for item in http_admin_configuration_snippet | regex_split -%}
  143. {% if item: -%}
  144. {{item}}
  145. {% endif -%}
  146. {% endfor %}
  147. http_end_configuration_snippet: |
  148. {% if not http_end_configuration_snippet: -%}
  149. {%- set http_end_configuration_snippet = "" -%}
  150. {%- endif -%}
  151. {%- for item in http_end_configuration_snippet | regex_split -%}
  152. {% if item: -%}
  153. {{item}}
  154. {% endif -%}
  155. {% endfor %}
  156. stream_configuration_snippet: |
  157. {% if not stream_configuration_snippet: -%}
  158. {%- set stream_configuration_snippet = "" -%}
  159. {%- endif -%}
  160. {%- for item in stream_configuration_snippet | regex_split -%}
  161. {% if item: -%}
  162. {{item}}
  163. {% endif -%}
  164. {% endfor %}
  165. http:
  166. enable_access_log: {{ enable_access_log | default("false") }}
  167. access_log: {{ access_log | default("/dev/stdout") }}
  168. access_log_format: {{ access_log_format | default('\"$remote_addr - $remote_user [$time_local] $http_host \\"$request\\" $status $body_bytes_sent $request_time \\"$http_referer\\" \\"$http_user_agent\\" $upstream_addr $upstream_status $upstream_response_time \\"$upstream_scheme://$upstream_host$upstream_uri\\"\"') }}
  169. access_log_format_escape: {{ access_log_format_escape | default("default") }}
  170. keepalive_timeout: {{ keepalive_timeout | default("60s") }}
  171. client_header_timeout: {{ client_header_timeout | default("60s") }}
  172. client_body_timeout: {{ client_body_timeout | default("60s") }}
  173. client_max_body_size: {{ client_max_body_size | default(0) }}
  174. send_timeout: {{ send_timeout | default("10s") }}
  175. underscores_in_headers: {{ underscores_in_headers | default("on") }}
  176. real_ip_header: {{ real_ip_header | default("X-Real-IP") }}
  177. real_ip_recursive: {{ real_ip_recursive | default("off") }}
  178. real_ip_from:
  179. - 127.0.0.1
  180. - "unix:"
  181. {% if not real_ip_from: -%}
  182. {%- set real_ip_from = "" -%}
  183. {%- endif -%}
  184. {%- for item in real_ip_from | regex_split -%}
  185. {% if item: -%}
  186. - {{item}}
  187. {% endif -%}
  188. {% endfor %}
  189. custom_lua_shared_dict:
  190. {% if not custom_lua_shared_dict: -%}
  191. {%- set custom_lua_shared_dict = "" -%}
  192. {%- endif -%}
  193. {%- for item in custom_lua_shared_dict | regex_split -%}
  194. {{ item }}
  195. {% endfor %}
  196. proxy_ssl_server_name: {{ proxy_ssl_server_name | default("true") }}
  197. upstream:
  198. keepalive: {{ upstream_keepalive | default(320) | int }}
  199. keepalive_requests: {{ upstream_keepalive_requests | default(1000) | int }}
  200. keepalive_timeout: {{ upstream_keepalive_timeout | default("60s") }}
  201. charset: {{ charset | default("utf-8") }}
  202. variables_hash_max_size: {{ variables_hash_max_size | default(2048) | int }}
  203. lua_shared_dict:
  204. internal-status: 10m
  205. plugin-limit-req: 10m
  206. plugin-limit-count: 10m
  207. prometheus-metrics: 32m
  208. plugin-limit-conn: 10m
  209. upstream-healthcheck: 10m
  210. worker-events: 10m
  211. lrucache-lock: 10m
  212. balancer-ewma: 10m
  213. balancer-ewma-locks: 10m
  214. balancer-ewma-last-touched-at: 10m
  215. plugin-limit-count-redis-cluster-slot-lock: 1m
  216. tracing_buffer: 10m
  217. plugin-api-breaker: 10m
  218. etcd-cluster-health-check: 10m
  219. discovery: 1m
  220. jwks: 1m
  221. introspection: 10m
  222. access-tokens: 1m
  223. etcd:
  224. host:
  225. - "{{ etcd_host }}"
  226. prefix: {{ etcd_prefix | default("/apisix") }}
  227. timeout: {{ etcd_timeout | default(30) }}
  228. resync_delay: {{ etcd_resync_delay | default(5) | int }}
  229. health_check_timeout: {{ etcd_health_check_timeout | default(10) | int }}
  230. user: {{ etcd_user | default("tapisix") }}
  231. password: {{ etcd_password | default("") }}
  232. tls:
  233. verify: {{ etcd_tls_verify | default("false") }}
  234. graphql:
  235. max_size: 1048576
  236. plugins:
  237. - client-control
  238. - ext-plugin-pre-req
  239. - zipkin
  240. - request-id
  241. - fault-injection
  242. - serverless-pre-function
  243. - batch-requests
  244. - cors
  245. - ip-restriction
  246. - ua-restriction
  247. - referer-restriction
  248. - uri-blocker
  249. - request-validation
  250. - openid-connect
  251. - wolf-rbac
  252. - hmac-auth
  253. - basic-auth
  254. - jwt-auth
  255. - key-auth
  256. - consumer-restriction
  257. - authz-keycloak
  258. - proxy-mirror
  259. - proxy-cache
  260. - proxy-rewrite
  261. - api-breaker
  262. - limit-conn
  263. - limit-count
  264. - limit-req
  265. - gzip
  266. - server-info
  267. - traffic-split
  268. - redirect
  269. - response-rewrite
  270. - grpc-transcode
  271. - prometheus
  272. - echo
  273. - http-logger
  274. - skywalking-logger
  275. - sls-logger
  276. - tcp-logger
  277. - kafka-logger
  278. - syslog
  279. - udp-logger
  280. - serverless-post-function
  281. - ext-plugin-post-req
  282. {% if not custom_plugins: -%}
  283. {%- set custom_plugins = "" -%}
  284. {%- endif -%}
  285. {%- for item in custom_plugins | regex_split -%}
  286. {% if item: -%}
  287. - {{item}}
  288. {% endif -%}
  289. {% endfor %}
  290. stream_plugins:
  291. - ip-restriction
  292. - limit-conn
  293. - mqtt-proxy
  294. {% if not custom_stream_plugins: -%}
  295. {%- set custom_stream_plugins = "" -%}
  296. {%- endif -%}
  297. {%- for item in custom_stream_plugins | regex_split -%}
  298. {% if item: -%}
  299. - {{item}}
  300. {% endif -%}
  301. {% endfor %}
  302. plugin_attr:
  303. prometheus:
  304. export_uri: {{ prometheus_export_uri | default("/apisix/prometheus/metrics") }}
  305. enable_export_server: {{ prometheus_enable_export_server | default("false") }}
  306. export_addr:
  307. ip: 0.0.0.0
  308. port: {{ prometheus_export_port | default(9091) | int}}
  309. server-info:
  310. report_interval: {{ serveir_info_report_interval | default(60) | int }}
  311. report_ttl: {{ serveir_info_report_ttl | default(3600) | int }}
  312. discovery:
  313. eureka:
  314. host:
  315. {% if not eureka_host: -%}
  316. {%- set eureka_host = "http://eureka.svc.local" -%}
  317. {%- endif -%}
  318. {%- set host_list = eureka_host | regex_split -%}
  319. {%- for item in host_list -%}
  320. {% if item: -%}
  321. - {{item}}
  322. {% endif -%}
  323. {% endfor %}
  324. prefix: "/eureka/"
  325. fetch_interval: {{ eureka_fetch_interval | default(5) | int }}
  326. weight: {{ eureka_weight | default(100) | int }}
  327. timeout:
  328. connect: {{ eureka_connect_timeout | default(2000) | int }}
  329. send: {{ eureka_send_timeout | default(2000) | int }}
  330. read: {{ eureka_read_timeout | default(5000) | int }}
  331. polaris:
  332. cache_size: {{ polaris_cache_size | default(1000) | int }}
  333. update_time: {{ polaris_update_time | default(3) | int }}
  334. max_cache_time: {{ polaris_max_cache_time | default(5) | int }}

将上述代码保存为 config-template.yaml,即 Jinja2 的渲染模板。这个模板基本覆盖到了每一个 APISIX 配置文件的内容,能够默认的就都设置了默认值,减少配置工作量。对于行数可变的多行配置,比如http_configuration_snippet 和plugins 等,我们也是通过 Jinja2 里面的遍历+英文逗号分隔的方法来支持动态配置。

二、Python 脚本

简单写一个从环境变量中提取 APISIX 变量、然后通过 Jinja2 渲染成实际配置文件的脚本:

  1. # -*- coding:utf-8 -*-
  2. """APISIX 配置文件生成工具
  3. 功能描述:通过获取环境变量生成 APISIX 的配置文件。
  4. """
  5. import sys
  6. import os
  7. import requests
  8. from jinja2 import Environment, FileSystemLoader
  9. reload(sys)
  10. sys.setdefaultencoding('utf-8')
  11. class Utils():
  12. def __init__(self):
  13. self.path = os.path.dirname(os.path.abspath(__file__))
  14. self.template_environment = Environment(
  15. autoescape=False,
  16. loader=FileSystemLoader(os.path.join(self.path, '')),
  17. trim_blocks=False)
  18. self.template_environment.filters["regex_split"] = self.regex_split
  19. def render_template(self, template_filename, context):
  20. return self.template_environment.get_template(
  21. template_filename).render(context)
  22. def gen_yaml_content(self, template, context):
  23. yaml = self.render_template(template, context)
  24. return yaml
  25. def regex_split(self, input):
  26. return re.split(r"[,|\n]", input)
  27. def get_env_list(self, prefix=None, replace=True):
  28. """ 获取环境变量
  29. :param prefix: 指定目标变量的前缀
  30. :param replace:指定前缀后,键名是否去掉前缀
  31. """
  32. env_dict = os.environ
  33. if prefix:
  34. env_list = {}
  35. for key in env_dict:
  36. if prefix in key:
  37. if replace:
  38. env_list[key.replace(prefix, "")] = env_dict[key]
  39. else:
  40. env_list[key] = env_dict[key]
  41. return env_list
  42. else:
  43. return dict(env_dict)
  44. if __name__ == "__main__":
  45. utils = Utils()
  46. try:
  47. config_list = utils.get_env_list(prefix="apisix_")
  48. content = utils.gen_yaml_content("config-template.yaml", config_list)
  49. with open("/usr/local/apisix/conf/config.yaml", "w") as f:
  50. f.write(content)
  51. except Exception as error: # pylint: disable=broad-except
  52. exit("Failed to generate configuration file: {}".format(error))

脚本会从运行系统的环境变量中提取前缀为 apisix_ 的环境变量列表, 然后通过 Jinja2 填充到配置模板中,最终生成 APISIX 的配置文件 config.yaml,整体非常简单。

我们在公司内部其实是有配置中心的,所以在实际使用中,我们是从配置中心去拉取配置然后来渲染的,这里只是分享一个方案,因此就用环境变量简单示范一下了。确实需要使用的朋友,可以将脚本改成从配置中心拉取,比如 Apollo、Zookeeper、Consul、DB 等,难度也非常小。

三、Docker 镜像

上面展示了通过执行 Python 脚本提取环境变量,快速生成 APISIX 配置文件的方案。接下来,我们将这个机制集成到 APISIX 的 Docker 镜像中,实现一个自动化配置的镜像。

1、Dockerfile 配置

Jinja2 需要 Python 环境的支持,所以这里选择 APISIX 官方的 Centos 镜像,默认自带了 Python2.7.5,只需要在这个基础上安装一下 Jinja2 插件即可。

  1. FROM apache/apisix:2.10.0-centos
  2. LABEL maintainer="Jager", description="支持环境变量设置任意配置的 APISIX 镜像。"
  3. RUN yum install -y python-jinja2
  4. # 自定义插件可以放到 plugins 目录,一并集成
  5. COPY plugins /usr/local/apisix/apisix/plugins
  6. COPY auto_conf /opt/auto_conf
  7. COPY docker-entrypoint.sh /
  8. ENTRYPOINT ["/docker-entrypoint.sh"]
  9. CMD ["/usr/local/openresty/bin/openresty", "-p", "/usr/local/apisix", "-g", "daemon off;"]

2、docker-entrypoint.sh

因为渲染时需要执行 Python 脚本的,因此需要在 ENTRYPOINT 这里插入相关执行命令,脚本内容如下:

  1. #!/bin/bash
  2. set -e
  3. # 启动前先进行 Jinja2 渲染
  4. cd /opt/auto_conf && \
  5. python make_conf.py >/dev/stderr 2>&1 || exit 1
  6. # APISIX 初始化
  7. /usr/bin/apisix init >/dev/stderr 2>&1 && \
  8. /usr/bin/apisix init_etcd >/dev/stderr 2>&1 || exit 1
  9. # 执行真正的启动命令
  10. exec "$@"

3、自定义插件

在实际使用场景中,我们可能还有一些自定义的 APISIX 插件,也可以在制作这个 Docker 镜像过程中一并集成进去,比如张戈博客前两篇文章分享的 2 个实用插件:

APISIX 插件开发之精细化限速插件
APISIX 插件开发之 Kong 网关 HMAC 鉴权插件(附客户端 SDK)

整个镜像配置我已经上传到 github,有需要的同学可以自行 fork 改造:https://github.com/jagerzhang/apisix-docker

四、运行示例

看懂了前面的同学应该已经对如何运行是没什么疑问了。这里还是简单贴一下使用方法,方便第一次接触的同学快速上手。

其实非常简单,需要配置 APISIX 的哪个参数,只需要在 config-template.yaml 这个模板中去找对应的变量名,比如需要配置 etcd 地址,我们在 config-template.yaml 找到对应的变量名称是 etcd_host,而且支持通过英文逗号分隔来配置多条。

因此,启动命令如下:

  1. docker run --name=apisix_test -d \
  2. -e apisix_etcd_host=http://127.0.0.1:2379,http://127.0.0.2:2379,http://127.0.0.3:2379
  3. <apisix 镜像名>

总之,需要改啥配置就去 config-template.yaml 找对应的变量名,然后在指定系统环境变量 apisix_<变量名>的值,如果是多行则用英文逗号分隔即可。如果在 config-template.yaml 没找到,那么就参考官方config-default.yaml来修改 Jinja2 模板:config-template.yaml。

五、其他

本文分享的方法虽然非常实用,实际上还需要安装 jinja2 然后跑 Python 脚本, 并非最优雅的方案。如果是会写 lua 脚本的朋友,可以通过lua-resty-template改造下,那就完美了!有实现的朋友记得给张戈留言分享一下成果。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/985931
推荐阅读
相关标签
  

闽ICP备14008679号