RSS资讯

码农街博客

如何在 Apache APISIX 中集成 Keycloak 实现身份认证

<h2>什么是 Keycloak</h2> <p><a href="https://www.keycloak.org/">Keycloak</a> 是一个针对现代应用程序和服务的开源身份和访问管理解决方案。Keycloak 支持单点登录(Single-Sign On),因此服务可以通过 OpenID Connect、OAuth 2.0 等协议对接 Keycloak。同时 Keycloak 也支持集成不同的身份认证服务,例如 Github、Google 和 Facebook 等。</p> <p>另外 Keycloak 也支持用户联邦功能,可以通过 LDAP 或 Kerberos 来导入用户。更多 Keycloak 内容可以参考<a href="https://www.keycloak.org/documentation">官方文档介绍</a>。</p> <h2>如何使用</h2> <h3>环境准备</h3> <p>在进行如下步骤前,请确保环境中已启动 Apache APISIX。</p> <h4>启动 Keycloak</h4> <p>这里我们使用 <code>docker-compose</code> 将 Keycloak 与其所依赖的的 PostgreSQL 一并启动。</p> <pre><code class="language-yaml">version: '3.7' services: postgres: image: postgres:12.2 container_name: postgres environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: password keycloak: image: jboss/keycloak:9.0.2 container_name: keycloak environment: DB_VENDOR: POSTGRES DB_ADDR: postgres DB_DATABASE: keycloak DB_USER: keycloak DB_PASSWORD: password KEYCLOAK_USER: admin KEYCLOAK_PASSWORD: password PROXY_ADDRESS_FORWARDING: "true" ports: - 8080:8080 depends_on: - postgres </code></pre> <pre><code class="language-shell">docker-compose up </code></pre> <p>执行完毕后需要确认 Keycloak 和 PostgreSQL 是否已成功启动。</p> <pre><code class="language-shell">docker-compose ps </code></pre> <h4>配置 Keycloak</h4> <p>Keycloak 启动完成之后,使用浏览器访问 "http://127.0.0.1:8080/auth/admin/",并键入 <code>admin/password</code> 账号密码进行登录管理员控制台。</p> <h5>创建 realm</h5> <p>首先,创建一个名称为 <code>apisix_test_realm</code>的 <code>realm</code>。在 Keycloak 中,<code>realm</code> 是一个专门用来管理项目的工作区,不同 <code>realm</code>之间的资源是相互隔离的。</p> <p>Keycloak 中 <code>realm</code> 分为两类:一类是 <code>master realm</code>,由 Keycloak 刚启动时创建,用于管理 admin 账号以及创建其他的 <code>realm</code>。第二类是 <code>other realm</code>,由 <code>master realm</code> 中的 admin 创建,可以在该 <code>realm</code> 中进行用户和应用的创建并进行管理和使用。更多细节可参考 <a href="https://www.keycloak.org/guides#getting-started">Keycloak 中 realm 和 users</a> 相关内容。</p> <p><img src="https://static.apiseven.com/202108/1639101202459-72803240-b358-4c69-a9ca-4b6751a8547d.png" alt="创建 realm" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1639101243617-0498379f-392e-4837-8f37-eee558c21e3d.png" alt="编辑 realm 名称" referrerpolicy="no-referrer"></p> <h5>创建 Client</h5> <p>接下来需要创建 <code>OpenID Connect Client</code>。在 Keycloak 中,Client 表示允许向 Keycloak 发起身份认证的客户端。</p> <p>在本示例场景中,<code>Apache APISIX</code> 相当于一个客户端,负责向 Keycloak 发起身份认证请求,因此我们创建一个名称为 <code>apisix</code> 的 Client。关于 Client 更多细节可参考 <a href="https://www.keycloak.org/docs/latest/server_admin/#_oidc_clients">Keycloak OIDC Clients</a>。</p> <p><img src="https://static.apiseven.com/202108/1639101288379-9a46b92a-294e-4b40-ac7e-408284a3d0ad.png" alt="创建 OpenID Client" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1639101327347-c8ab463a-1cb0-4eb0-a26f-17d7c0c54846.png" alt="创建 Client 名称" referrerpolicy="no-referrer"></p> <h5>配置 Client</h5> <p>Client 创建完成后,需要为 Client 配置 Apache APISIX 的访问类型。</p> <p>在 Keycloak 中 <code>Access Type</code> 分为三类:</p> <ol> <li><strong>confidential</strong>。适用于需要执行浏览器登录的应用,客户端会通过 <code>client secret</code> 来获取 <code>access token</code> , 多运用于服务端渲染的 web 系统。</li> <li><strong>public</strong>。适用于需要执行浏览器登录的应用,多运用于使用 vue 和 react 实现的前端项目。</li> <li><strong>bearer-only</strong>。适用于不需要执行浏览器登录的应用,只允许携带 <code>bearer token</code> 访问,多运用于 RESTful API 的使用场景。</li> </ol> <p>更多关于 Client 设置细节可参考 <a href="https://www.keycloak.org/docs/latest/server_admin/#advanced-settings">Keycloak OIDC Clients 高级设置</a>。</p> <p>因为我们使用了 Apache APISIX 作为服务端的 Client, 因此可以选择类型一或类型三(这里以类型一为例进行演示)。</p> <p><img src="https://static.apiseven.com/202108/1639101355171-e368730b-2a72-4c4d-9397-cf4a1c8f2806.png" alt="配置 Client 类型" referrerpolicy="no-referrer"></p> <h5>创建 User</h5> <p>Keycloak 支持对接其他第三方的用户系统,例如 Google 和 Facebook。或者使用 LDAP 的方式进行导入或手动创建用户,这里我们使用「手动创建用户」来进行演示。</p> <p><img src="https://static.apiseven.com/202108/1639101385277-b2f578c0-e68a-4945-83ac-7a77145bb056.png" alt="创建用户" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1639101406281-724bbb50-96fc-4aa8-aec1-9414f83c199d.png" alt="添加用户相关信息" referrerpolicy="no-referrer"></p> <p>在 Credentials 页面设置用户的密码。</p> <p><img src="https://static.apiseven.com/202108/1639101430209-d289459a-5014-4a2d-864f-7917b84b1c0c.png" alt="设置用户密码" referrerpolicy="no-referrer"></p> <h4>创建路由</h4> <p>Keycloak 配置完成后,需要在 Apache APISIX 中创建路由并开启 <code>Openid-Connect</code> 插件,具体关于该插件的配置文档可以参考 <a href="https://apisix.apache.org/docs/apisix/plugins/openid-connect">Apache APISIX OpenID-Connect 插件</a>。</p> <h4>获取 client_id 和 client_secret</h4> <p><img src="https://static.apiseven.com/202108/1639101454807-ff8c8b77-bdac-4ac6-966e-a2f5e2418b7a.png" alt="获取 Client 相关信息" referrerpolicy="no-referrer"></p> <p>上图配置中:</p> <ul> <li><code>client_id</code> 为之前创建 Client 时使用的名称,即 <code>apisix</code>;</li> <li><code>client_secret</code> 则需要进入 Clients-apisix-Credentials 中获取,例如:<code>d5c42c50-3e71-4bbe-aa9e-31083ab29da4</code>。</li> </ul> <h4>获取 discovery 配置项</h4> <p><img src="https://static.apiseven.com/202108/1639101585273-7eb31728-fe4c-4af3-84d1-76c1a97e7e35.png" alt="获取配置" referrerpolicy="no-referrer"></p> <p>进入 Realm Settings-General-Endpoints 中,选择 <code>OpenID Endpoint Configuration</code> 链接,复制该链接指向的地址。例如:<code>http://127.0.0.1:8080/auth/realms/apisix_test_realm/.well-known/openid-configuration</code>。</p> <h5>创建路由并开启插件</h5> <p>使用如下命令访问 Apache APISIX Admin 接口来创建一条路由,设置上游为 <code>httpbin.org</code>,并开启插件 OpenID Connect 用于身份认证。</p> <blockquote> <p>注意:如果创建 Client 时,选择 <code>bearer-only</code> 作为 <code>Access Type</code>,在配置路由是需要将 <code>bearer_only</code> 设置为 true,此时访问 Apache APISIX 将不会跳转到 Keycloak 登录界面。</p> </blockquote> <pre><code class="language-shell">curl -XPOST 127.0.0.1:9080/apisix/admin/routes -H "X-Api-Key: edd1c9f034335f136f87ad84b625c8f1" -d '{ "uri":"/*", "plugins":{ "openid-connect":{ "client_id":"apisix", "client_secret":"d5c42c50-3e71-4bbe-aa9e-31083ab29da4", "discovery":"http://127.0.0.1:8080/auth/realms/apisix_test_realm/.well-known/openid-configuration", "scope":"openid profile", "bearer_only":false, "realm":"apisix_test_realm", "introspection_endpoint_auth_method":"client_secret_post", "redirect_uri":"http://127.0.0.1:9080/" } }, "upstream":{ "type":"roundrobin", "nodes":{ "httpbin.org:80":1 } } }' </code></pre> <h3>访问测试</h3> <p>上述配置完成后,我们就可以在 Apache APISIX 中进行相关的测试访问了。</p> <h4>访问 Apache APISIX</h4> <p>使用浏览器访问 <code>http://127.0.0.1:9080/image/png</code>。</p> <p>由于开启了 OpenID-Connect 插件,并且设置了 <code>bearer-only</code> 为 <code>false</code> 。因此第一次访问该路径时, Apache APISIX 将重定向到 Keycloak 中 <code>apisix_test_realm</code> 中配置的登录界面,进行用户登录请求。</p> <p><img src="https://static.apiseven.com/202108/1639101623370-cc668e0f-0c2c-469c-9a3e-3118c271d63f.png" alt="登录页面" referrerpolicy="no-referrer"></p> <p>输入之前配置 Keycloak 时创建的 User peter,即可完成用户登录。</p> <h4>访问成功</h4> <p>登录成功后,浏览器又会将链接重定向到 "<a href="http://127.0.0.1:9080/image/png">http://127.0.0.1:9080/image/png</a>"。并成功访问到该图片内容,该内容与上游 "<a href="http://httpbin.org/image/png">http://httpbin.org/image/png</a>" 一致。</p> <p><img src="https://static.apiseven.com/202108/1639101644015-6d541202-dfff-4de3-ad47-f49dd65911a6.png" alt="访问成功" referrerpolicy="no-referrer"></p> <h4>登出账号</h4> <p>测试完毕后,使用浏览器访问 "http:/127.0.0.1:9080/logout" 进行账号登出。</p> <blockquote> <p>注意:登出路径可通过 OpenID-Connect 插件配置中的 <code>logout_path</code> 指定,默认为 <code>logout</code>。</p> </blockquote> <h2>总结</h2> <p>本文通过详细的步骤为大家展示了如何在 Apache APISIX 使用 OpenID-Connect 协议和 Keycloak 进行身份认证。通过对接 Keycloak,Apache APISIX 仅需通过配置即可实现对使用者和应用服务进行认证与鉴权,从而大大减少了相关开发工作。</p> <p>更多关于 Apache APISIX 中的身份认证功能实现也可参考文章<a href="https://apisix.apache.org/zh/blog/2021/08/16/using-the-apache-apisix-openid-connect-plugin-for-centralized-authentication/">《使用 Apache APISIX 和 Okta 来实现集中式身份认证》</a>。</p>

Apache APISIX 集成 Kafka 实现高效率实时日志监控

<p>Apache Kafka 是由 Apache 管理的开源流处理平台,由 Scala 和 Java 编写,为处理实时数据提供了统一、高吞吐、低延迟的功能特性。</p> <p>其持久化层本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,这使它作为企业级基础设施来处理流式数据非常有价值。目前已被数千家公司用于高性能数据管道、流分析、数据集成和任务关键型应用程序等领域。</p> <h2>实现方式:kafka-logger</h2> <p>Apache APISIX 早在 1.2 版本开始就已经提供了 <code>kafka-logger</code> 插件的支持,其后又经过多次功能强化,目前已具备非常成熟且完善的功能。支持将 API 请求日志,甚至请求体和响应体以 JSON 格式推送至 Kafka 集群中。</p> <p>使用 <code>kafka-logger</code> 时,用户可以发送多种数据并自定义发送的日志格式,同时还支持以批处理的方式打包发送日志或进行自动重试等功能。</p> <h2>如何使用</h2> <h3>步骤1:启动 Kafka 集群</h3> <p>本文示例只演示了一种启动方式,其他启动方式细节可参考 <a href="https://kafka.apache.org/documentation/#quickstart">Kafka 官方文档</a>。</p> <pre><code class="language-yaml"># 使用 docker-compose 启动一个具有 1个 zookeeper 节点、3个 kafka 节点的集群 # 同时还启动一个 EFAK 用于数据监控。 version: '3' services: zookeeper: image: confluentinc/cp-zookeeper:6.2.1 hostname: zookeeper ports: - "2181:2181" environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_SERVER_ID: 1 ZOOKEEPER_SERVERS: zookeeper:2888:3888 kafka1: image: confluentinc/cp-kafka:6.2.1 hostname: kafka1 ports: - "9092:9092" environment: KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka1:19092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_BROKER_ID: 1 KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" depends_on: - zookeeper kafka2: image: confluentinc/cp-kafka:6.2.1 hostname: kafka2 ports: - "9093:9093" environment: KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka2:19093,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_BROKER_ID: 2 KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" depends_on: - zookeeper kafka3: image: confluentinc/cp-kafka:6.2.1 hostname: kafka3 ports: - "9094:9094" environment: KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka3:19094,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9094 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_BROKER_ID: 3 KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" depends_on: - zookeeper efak: image: nickzurich/kafka-eagle:2.0.9 hostname: efak ports: - "8048:8048" depends_on: - kafka1 - kafka2 - kafka3 </code></pre> <h3>步骤2:创建 Topic</h3> <p>接下来我们创建 <code>test Topic</code> 用于收集日志。</p> <p><img src="https://static.apiseven.com/202108/1642390784736-562187ed-ade9-4a2f-96e1-c79556f9dd7d.png" alt="test Topic" referrerpolicy="no-referrer"></p> <h3>步骤3:创建路由并开启插件</h3> <p>通过下方命令可进行路由创建与 <code>kafka-logger</code> 插件的开启。</p> <pre><code class="language-shell">curl -XPUT 'http://127.0.0.1:9080/apisix/admin/routes/r1' \ --header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ --header 'Content-Type: application/json' \ --data-raw '{ "uri": "/*", "plugins": { "kafka-logger": { "batch_max_size": 1, "broker_list": { "127.0.0.1": 9092 }, "disable": false, "kafka_topic": "test", "producer_type": "sync" } }, "upstream": { "nodes": { "httpbin.org:80": 1 }, "type": "roundrobin" } }' </code></pre> <p>上述代码中配置了 <code>kafka broker</code> 地址、目标 Topic、同步的生产模式和每一批次包含的最大日志数量。这里我们可以先将 <code>batch_max_size</code> 设置为 1,即每产生一条日志就向 Kafka 写入一条消息。</p> <p>通过上述设置,就可以实现将 <code>/*</code> 路径下的 API 请求日志发送至 Kafka 的功能。</p> <h3>步骤4:发送请求</h3> <p>接下来我们通过 API 发送一些请求,并记录下请求次数。</p> <pre><code class="language-shell"># 向 API 发送 10 次请求 curl http://127.0.0.1:9080/get </code></pre> <p>通过下图可以看到,有一些日志消息已经被写入到我们创建的 <code>test topic</code>2 中。点击查看日志内容,可以发现上述进行的 API 请求日志已经被写入了。</p> <p><img src="https://static.apiseven.com/202108/1642390828394-721eccfa-ab02-4f8f-a0d8-8039e0eaabc1.png" alt="发送请求-1" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1642390874028-89683dfb-ab16-48cd-92de-496cc60df3b5.png" alt="发送请求-2" referrerpolicy="no-referrer"></p> <h4>自定义日志结构</h4> <p>当然,在使用过程中我们也可以通过 <code>kafka-logger</code> 插件提供的元数据配置,来设置发送至 Kafka 的日志数据结构。通过设置 <code>log_format</code> 数据,可以控制发送的数据类型。</p> <p>比如以下数据中的 <code>$host</code>、<code>$time_iso8601</code> 等,都是来自于 Nginx 提供的内置变量;也支持如 <code>$route_id</code> 和 <code>$service_id</code> 等 Apache APISIX 提供的变量配置。</p> <pre><code class="language-shell">curl -XPUT 'http://127.0.0.1:9080/apisix/admin/plugin_metadata/kafka-logger' \ --header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ --header 'Content-Type: application/json' \ --data-raw '{ "log_format": { "host": "$host", "@timestamp": "$time_iso8601", "client_ip": "$remote_addr", "route_id": "$route_id" } }' </code></pre> <p>通过发送请求进行简单测试,可以看到上述日志结构设置已生效。目前 Apache APISIX 提供多种日志格式模板,在配置上具有极大的灵活性,更多日志格式细节可参考 <a href="https://apisix.apache.org/docs/apisix/plugins/kafka-logger#metadata">Apache APISIX 官方文档</a>。</p> <p><img src="https://static.apiseven.com/202108/1642390899127-d1eb560a-499e-4a9f-9227-4063ba711e2d.png" alt="自定义日志结构" referrerpolicy="no-referrer"></p> <h3>关闭插件</h3> <p>如使用完毕,只需移除路由配置中 <code>kafka-logger</code> 插件相关配置并保存,即可关闭路由上的插件。得益于 Apache APISIX 的动态化优势,开启关闭插件的过程都不需要重启 Apache APISIX,十分方便。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["GET"], "uri": "/hello", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' </code></pre> <h2>总结</h2> <p>本文为大家介绍了关于 kafka-logger 插件的功能前瞻与使用步骤,更多关于 kafka-logger 插件说明和完整配置列表,可以参考官方文档。</p> <p>也欢迎随时在 <a href="https://github.com/apache/apisix/discussions">GitHub Discussions</a> 中发起讨论,或通过<a href="https://apisix.apache.org/zh/docs/general/subscribe-guide">邮件列表</a>进行交流。</p>

强强联合!APISIX 集成 SkyWalking 打造全方位日志处理

<p>在可观测性领域,通常采用 Metrics、Logger 与 Tracing 三大方向的数据收集与分析,以达到洞察应用运行状态的目的,Apache SkyWalking 的日志处理正好具备了以上三方面。</p> <p>Apache APISIX 早在 1.4 版本就已经集成 Apache SkyWaling Tracing 能力,并在后续版本中加入了错误日志和访问日志收集等功能。如今随着 Apache SkyWalking 对 Metrics 的支持,能够帮助 Apache APISIX 在集成模式下实现一站式可观测方案,同时覆盖到日志、度量和调用追踪。</p> <h2>功能开发背景</h2> <p>熟悉 Apache APISIX 的小伙伴应该知道,Apache APISIX 在运行中会产出两种日志,即访问日志和错误日志。</p> <p>访问日志记录着每个请求的详细信息,属于<strong>请求范围</strong>内产生的日志,因此可以直接与 Tracing 关联。而错误日志则是 Apache APISIX <strong>运行时</strong>产出日志信息,是整个应用级别日志,但无法确保能百分百关联到请求上。</p> <p>目前 Apache APISIX 提供了非常丰富的日志处理插件,包括 TCP/HTTP/Kafka 等收集上报插件,但它们与 Tracing 关联都比较弱。以 Apache SkyWalking 为例,提取 Apache APISIX 端日志记录中的 SkyWalking Tracing Conetxt Header 并输出到文件系统,之后利用日志处理框架(fluentbit)将日志转成 SkyWalking 可接受的日志格式。后续从中解析提取 Tracing Context,从而获得 Tracing ID 进而与 Trace 建立联系。</p> <p>显然,上述方式处理流程比较繁琐复杂,还需要额外转换日志格式。为此,在 <a href="https://github.com/apache/apisix/pull/5550">PR#5500</a> 中我们实现了将 Apache SkyWalking 访问日志接入 Apache APISIX 插件生态,方便用户在使用 Apache APISIX 中更方便地利用 Apache SkyWalking 进行收集和处理相关日志。</p> <h2>全新日志插件介绍</h2> <h3>SkyWalking Logger 插件</h3> <p>SkyWalking Logger 插件能够解析 SkyWalking Tracing Context Header,并将相关 Tracing Context 信息打印到日志中,从而实现日志与调用链建立关联。</p> <p>通过使用此插件,可以实现在下游已经集成 Apache SkyWalking 的情况下,Apache APISIX 即便没有打开 SkyWalking Tracing 插件也能获取到 SkyWalking Tracing Context 并实现与 Tracing 关联。</p> <p><img src="https://static.apiseven.com/202108/1638781626018-da50a39d-da16-4914-b4f5-8ac9b4312e19.png" alt="日志内容" referrerpolicy="no-referrer"></p> <blockquote> <p>上图 Content 为日志内容,这里使用了 Apache APISIX metadata 配置来收集 Request 相关信息。后续可通过 Plugin Metadata 修改 Log Format 定制日志内容,具体可以参考<a href="https://apisix.apache.org/docs/apisix/plugins/skywalking-logger#metadata">官方文档</a>。</p> </blockquote> <h4>使用方法</h4> <p>在使用该插件时,由于 SkyWalking 插件默认为「不开启」,所以需要手动修改 <code>conf/default-apisix.yaml</code> 文件中 <code>plugins</code> 章节来解开SkyWalking 注释从而启用插件。</p> <pre><code class="language-yaml">plugins: ... - skywalking ... </code></pre> <p>之后便可使用 SkyWalking Tracing 插件直接得到 Tracing 数据,以便接下来验证 Logging 插件相关功能是否能够正常开启与工作。</p> <h5>步骤一:创建路由</h5> <p>接下来创建一个路由,并绑定 SkyWalking Tracing 插件和 SkyWalking Logging 插件。关于插件具体配置细节可以参考 <a href="https://apisix.apache.org/docs/apisix/plugins/skywalking-logger">Apache APISIX 官方文档</a>,这里不再赘述。</p> <pre><code class="language-shell">curl -X PUT 'http://192.168.0.108:9080/apisix/admin/routes/1001' \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ -H 'Content-Type: application/json' \ -d '{ "uri": "/get", "plugins": { "skywalking": { "sample_ratio": 1 }, "skywalking-logger": { "endpoint_addr": "http://127.0.0.1:12800" } }, "upstream": { "type": "roundrobin", "nodes": { "httpbin.org:80": 1 } } }' </code></pre> <h5>步骤二:日志处理</h5> <p>在 Apache SkyWalking 侧,可以使用 LAL(Logger Analysis Language)脚本进行日志处理,比如 Tag 提取、SkyWalking 元数据修正等。</p> <p>这里进行 Tag 提取主要是为了后续检索方便,以及在 Metrics 统计时添加相关依赖。可使用如下代码进行 SkyWalking LAL 脚本配置来完成 Tag 提取,更多关于 SkyWalking LAL 脚本使用方法可以参考 <a href="https://skywalking.apache.org/docs/main/latest/en/concepts-and-designs/lal/">Apache SkyWalking 官方文档</a>。</p> <pre><code class="language-yaml"># The default LAL script to save all logs, behaving like the versions before 8.5.0. rules: - name: default dsl: | filter { json { abortOnFailure false } extractor { tag routeId: parsed.route_id tag upstream: parsed.upstream tag clientIp: parsed.client_ip tag latency: parsed.latency } sink { } } </code></pre> <p>在 SkyWalking OAP Server 配置上述 LAL 脚本后将显示如下日志:</p> <p><img src="https://static.apiseven.com/202108/1638781696137-6ba3a486-08c0-49e1-bc57-e144f95918a2.png" alt="LAL 脚本日志显示" referrerpolicy="no-referrer"></p> <p>展开日志详情如下:</p> <p><img src="https://static.apiseven.com/202108/1638781751540-d597ace7-1de1-4baf-b361-1c136dfe5e05.png" alt="展开日志详情" referrerpolicy="no-referrer"></p> <p>通过上述操作可以看到,将 <code>routeId</code>,<code>upstream</code> 和 <code>clientIp</code> 以键值对形式展示,比在日志正文中直接查找要方便许多,还能用于 MAL 统计生成相关的度量指标。</p> <h3>SkyWalking Error Logger 插件</h3> <p>目前 error-log-logger 插件已支持 SkyWalking 日志格式,现在可以使用 error-log-logger 插件将 Apache APISIX 错误日志快捷接入到 Apache SkyWalking 中。目前,错误日志还无法获取 SkyWalking Tracing Context 信息,因此无法直接与 SkyWalking Tracing 产生关联。</p> <p>关于 Error Log 接入 SkyWalking 主要是为了能集中管理 Apache APISIX 的日志数据,同时也方便在 SkyWalking 内查看所有可观测性数据。</p> <p><img src="https://static.apiseven.com/202108/1638781827612-f7d88e0e-0159-44ba-bc1e-b718695bc3b8.png" alt="SkyWalking 面板查看" referrerpolicy="no-referrer"></p> <h4>使用方法</h4> <p>由于 error-log-logger 插件默认「不开启」,所以仍需要上文中提到的方法进行插件开启。</p> <pre><code class="language-yaml">plugins: ... - error-log-logger ... </code></pre> <h5>步骤一:绑定路由</h5> <p>启用之后,需要将插件绑定到路由或者 <code>global rules</code> 上,这里我们以「绑定路由」为例。</p> <pre><code class="language-shell">curl -X PUT 'http://192.168.0.108:9080/apisix/admin/plugin_metadata/error-log-logger' \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ -H 'Content-Type: application/json' \ -d '{ "inactive_timeout": 10, "level": "ERROR", "skywalking": { "endpoint_addr": "http://127.0.0.1:12800/v3/logs" } }' </code></pre> <blockquote> <p>注意这里 <code>endpoint_addr</code> 是 SkyWalking OAP Server 地址,需要带上 URI(即 <code>/v3/logs</code>)。</p> </blockquote> <h5>步骤二:LAL 处理</h5> <p>与 Access Log 处理方式基本一样,日志在送达 SkyWalking OAP Server 时,同样会经过 LAL 处理。因此,我们依然可以使用 SkyWalking LAL 脚本来分析处理日志信息。</p> <p>需要注意的是,Error Log 日志消息体使用文本格式。如果进行 Tags 提取,则需要使用正则表达式来完成。与 Access Log 处理消息正文的方式略有不同,Acces Log 使用 JSON 格式,可以直接使用 JSON 解析后引用 JSON 对象的字段,其他处理流程则大体一致。</p> <p>同时也可以利用 Tags 来优化显示效果与检索,方便后续使用 SkyWalking MAL 进行 Metrics 计算。</p> <pre><code class="language-json">rules: - name: apisix-errlog dsl: | filter { text { regexp "(?&lt;datetime&gt;\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(?&lt;level&gt;\\w+)\\] \\d+\\#\\d+:( \\*\\d+ \\[(?&lt;module&gt;\\w+)\\] (?&lt;position&gt;.*\\.lua:\\d+): (?&lt;function&gt;\\w+\\(\\)):)* (?&lt;msg&gt;.+)" } extractor { tag level: parsed.level if (parsed?.module) { tag module: parsed.module tag position: parsed.position tag function: parsed.function } } sink { } } </code></pre> <p>在 SkyWalking OAP Server 使用的 LAL 脚本之后,将会在日志中提取部分 Tags,效果如下图。</p> <p><img src="https://static.apiseven.com/202108/1638781886771-b98c80de-4ea2-4cf3-ad1c-26250da231f7.png" alt="提取 Tags" referrerpolicy="no-referrer"></p> <h2>总结</h2> <p>本文主要介绍了两款 Apache APISIX 集成 SkyWalking 的日志插件,为之后大家在 Apache APISIX 中进行日志处理提供更方便的操作与环境。希望通过本篇内容,大家可以对新功能有了更充分的理解,后续可以更方便地利用 Apache APISIX 进行可观测数据的集中管理。</p>

Apache APISIX 携手 RocketMQ 为实时 API 日志监控功能再下一城

<p>Apache RocketMQ 自 2016 年走入全球开发者视野以来,目前已发展成为电商、金融、教育、科技等多领域技术中台的核心数据底座。</p> <p>据不完全统计,国内用户(包括金融、保险、财富和券商等各领域百强企业)超过 70% 的企业都在核心应用链路上规模化部署了 Apache RocketMQ,包括全球 5 大云厂商也纷纷上线了有关 Apache RocketMQ 的云产品服务。</p> <p>除了常规应用于核心业务消息的处理,也有非常多的公司开始使用 Apache RocketMQ 进行日志处理与分析。</p> <h2>插件介绍</h2> <p>为了满足广大企业用户对于日志处理的需求,Apache APISIX 发布了基于 Apache RocketMQ 的日志插件 <code>rocketmq-logger</code>,支持将 API 接口请求日志以 JSON 形式推送给 RocketMQ 集群。</p> <p>该插件使用 RocketMQ 原生支持的 TCP 协议,通过 OpenResty 提供的无阻塞 TCP Socket API,实现了高并发、高性能访问等功能特性。</p> <p>同时,使用 <code>rocketmq-logger</code> 插件发送的 API 日志格式与其他日志插件相同,同样支持批量发送日志、自定义日志格式、支持重试等功能。</p> <p>此外,该插件还支持 TLS 加密传输,以及配置 AK、SK 认证方式访问 Apache RocketMQ,满足用户对于数据安全的需求。</p> <h2>如何使用</h2> <h3>启动 RocketMQ</h3> <p>首先在本地利用下述命令来启动 RocketMQ,具体详细步骤可参考<a href="https://rocketmq.apache.org/docs/quick-start/">官方文档</a>。</p> <pre><code class="language-shell">wget https://dlcdn.apache.org/rocketmq/4.9.2/rocketmq-all-4.9.2-bin-release.zip unzip rocketmq-all-4.9.2-bin-release.zip cd rocketmq-4.9.2/ nohup sh bin/mqnamesrv &amp; nohup sh bin/mqbroker -n 127.0.0.1:9876 -c conf/broker.conf &amp; </code></pre> <h3>在 Apache APISIX 中开启插件</h3> <p>在生产环境中只需执行一条命令,就可以为指定路由启用 <code>rocketmq-logger</code> 插件。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "plugins": { "rocketmq-logger": { "nameserver_list" : [ "127.0.0.1:9876" ], "topic" : "test", } }, "upstream": { "nodes": { "127.0.0.1:1980": 1 }, "type": "roundrobin" }, "uri": "/hello" }' </code></pre> <p>启用 <code>rocketmq-logger</code> 插件后,任何对端点 <code>URI/hello</code> 的请求都会将日志推送到 Apache RocketMQ 中。</p> <p>具体支持的参数详情可参考下方表格:</p> <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>nameserver_list</td> <td>object</td> <td>要推送的 rocketmq 的 nameserver 列表。</td> </tr> <tr> <td>topic</td> <td>string</td> <td>要推送的 topic。</td> </tr> <tr> <td>key</td> <td>string</td> <td>发送消息的keys。</td> </tr> <tr> <td>tag</td> <td>string</td> <td>发送消息的tags。</td> </tr> <tr> <td>timeout</td> <td>integer</td> <td>发送数据的超时时间。</td> </tr> <tr> <td>use_tls</td> <td>boolean</td> <td>是否开启TLS加密。</td> </tr> <tr> <td>access_key</td> <td>string</td> <td>ACL认证的access key,空字符串表示不开启ACL。</td> </tr> <tr> <td>secret_key</td> <td>string</td> <td>ACL认证的secret key。</td> </tr> </tbody> </table> <h4>插件元数据设置</h4> <p>当然,如果在使用过程中不想使用默认的日志格式,也可以对插件进行元数据设置。</p> <p>首先可以通过模板形式来调整相关日志格式。</p> <table> <thead> <tr> <th>名称</th> <th>默认值</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>log_format</td> <td>{"host": "$host", "@timestamp": "$time_iso8601", "client_ip": "$remote_addr"}</td> <td>以 JSON 格式的键值对来声明日志格式。对于值部分,仅支持字符串。如果是以 <code>$</code> 开头,则表明是要获取 <strong>APISIX</strong> 变量或 <a href="http://nginx.org/en/docs/varindex.html">Nginx 内置变量</a>。特别的,<strong>该设置是全局生效的</strong>,意味着指定 log_format 后,将对所有绑定 http-logger 的 Route 或 Service 生效。</td> </tr> </tbody> </table> <p>日志格式调整完成后,需要向 <code>/apisix/admin/plugin_metadata</code> 端点发出请求来更新元数据,具体可参考下方代码。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/rocketmq-logger -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "log_format": { "host": "$host", "@timestamp": "$time_iso8601", "client_ip": "$remote_addr" } }' </code></pre> <h2>禁用插件</h2> <p>如果您不再使用该插件,可通过在插件配置中删除相应的 JSON 配置来禁用 <code>rocketmq-logger</code> 插件。该过程无需重新启动服务,输入下方代码即可立即生效。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["GET"], "uri": "/hello", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' </code></pre>

在 KubeSphere 中使用 Apache APISIX Ingress 网关接入自定义监控

<p>11 月初,KubeSphere 发布了 3.2.0 版本,新版本为项目网关增配了整套监控及管理页面,同时引入了集群网关来提供集群层面全局的 Ingress 网关能力。</p> <p>为了让用户更了解如何在新版 KubeSphere 中部署使用第三方 Ingress Controller,本文将以 <a href="https://apisix.apache.org/docs/ingress-controller/getting-started/">Apache APISIX Ingress Controller</a> 为例,为大家展示通过 KubeSphere 快速为 Kubernetes 集群使用不同类型的网关并进行状态监控。</p> <h2>准备工作</h2> <h3>安装 KubeSphere</h3> <p>安装 KubeSphere 有两种方法:</p> <ol> <li><a href="https://kubesphere.com.cn/docs/quick-start/all-in-one-on-linux/">在 Linux 上直接安装</a></li> <li><a href="https://kubesphere.com.cn/docs/quick-start/minimal-kubesphere-on-k8s/">在已有 Kubernetes 中安装</a></li> </ol> <p>KubeSphere 最小化安装版本已经包含了监控模块,因此不需要额外启用,可以通过「系统组件」页面中的「监控」标签页确认安装状态。</p> <p><img src="https://static.apiseven.com/202108/1638255471644-e1327ffc-dbed-4890-a15c-819f28731fc9.png" alt="确认安装状态" referrerpolicy="no-referrer"></p> <h3>部署 httpbin 演示应用</h3> <p>由于需要演示网关的访问控制能力,我们必须要先有一个可以访问的应用作为网关的后台服务。这里我们使用 <a href="https://www.apiseven.com/blog/httpbin.org">httpbin.org</a> 提供的 <a href="https://hub.docker.com/r/kennethreitz/httpbin/">kennethreitz/httpbin</a> 容器应用作为演示应用。</p> <p>在 KubeSphere 中,我们可以先创建新的项目或使用已有的项目,进入项目页面后,选择「应用负载」下的「服务」直接创建无状态工作负载并生成配套服务。</p> <p><img src="https://static.apiseven.com/202108/1638255616585-b0f5a674-f06a-4b18-baf9-8d6006abeead.png" alt="创建服务" referrerpolicy="no-referrer"></p> <p>使用 <a href="https://hub.docker.com/r/kennethreitz/httpbin/">kennethreitz/httpbin</a> 容器默认的 <code>80</code> 端口作为服务端口,创建完成后确保在「工作负载」和「服务」页面下都可以看到 <code>httpbin</code> 的对应条目,如下图所示。</p> <p><img src="https://static.apiseven.com/202108/1638255786442-924bf704-9b9d-413f-9fc0-be6650a6ff4a.png" alt="服务" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1638255792974-7f354950-e34a-427a-9ff7-aa3af0a56dd6.png" alt="工作负载" referrerpolicy="no-referrer"></p> <h3>项目网关细节补充</h3> <p><strong>项目网关</strong>是 KubeSphere 3.0 之后上线的功能。KubeSphere 项目中的网关是一个 NGINX Ingress 控制器。KubeSphere 内置用于 HTTP 负载均衡的机制称为<strong>应用路由</strong>,它定义了从外部到集群服务的连接规则。如需允许从外部访问服务,用户可创建路由资源来定义 URI 路径、后端服务名称等信息。</p> <p>承接上文中已部署的 <code>httpbin</code> 服务项目,在「项目设置」中打开「网关设置」页面,然后执行「开启网关」操作。方便起见,直接选择 <code>NodePort</code> 作为「访问方式」即可。</p> <p><img src="https://static.apiseven.com/202108/1638256005754-d1e8bf9a-0ecc-4c6e-8ceb-0c25b04fef20.png" alt="项目设置" referrerpolicy="no-referrer"></p> <p>确定后回到网关页面,稍等片刻后刷新页面,可以得到如下图显示的部署完成状态,在这里可以看到 NodePort 默认被赋予了两个节点端口。接招我们通过右上角的「管理」按钮「查看详情」。</p> <p><img src="https://static.apiseven.com/202108/1638256011357-960f6852-31b3-4702-8911-17d07ec19d7b.png" alt="部署完成" referrerpolicy="no-referrer"></p> <p>此时我们看到的便是 3.2.0 版本关于项目/集群网关的新监控页面。下面我们就需要为 httpbin 服务创建应用路由。</p> <p>从「应用负载」进入「应用路由」页面,开始「创建」路由。为路由取名为 <code>httpbin</code> 后,我们指定一个方便测试的域名,并设置「路径」为 <code>/</code>, 选择「服务」<code>httpbin</code> 和「端口」<code>80</code>。</p> <p><img src="https://static.apiseven.com/202108/1638241684770-ce94fe24-58a6-4b9b-9507-d802713b4c38.png" alt="应用路由" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1638241689985-149fc2f7-456b-423c-8cfc-00800dc24917.png" alt="创建路由" referrerpolicy="no-referrer"></p> <p>直接下一步跳过高级设置后完成路由创建,可以得到如下图所示的 httpbin 应用路由项。</p> <p><img src="https://static.apiseven.com/202108/1638256382273-109728eb-4d19-4c2b-ab92-9a2909c3eff8.png" alt="应用路由项细节" referrerpolicy="no-referrer"></p> <p>接下来我们就可以通过项目网关的 NodePort 地址及指定域名(如这里是 <a href="http://httpbin.ui:32516/">http://httpbin.ui:32516</a>) 来访问 httpbin 应用服务,随意刷新或操作一下页面的请求生成功能,再进入网关的详情页面,便可以看到在「监控」面板上已经出现了网关的一些内置的监控指标展示。</p> <p><img src="https://static.apiseven.com/202108/1638256419345-48476f01-b293-401b-9e4f-8bf64a9fab90.png" alt="监控指标展示" referrerpolicy="no-referrer"></p> <h4>指定 NodePort 节点端口</h4> <p>对于公有云环境,如果使用 NodePort 方式向外暴露访问能力,开放端口通常是有限且受控的,因此对于网关所使用的 NodePort 我们需要对它进行修改。</p> <p>由于网关是被 KubeSphere 统一管理的,要修改网关服务的 NodePort 需要具备访问 <code>kubesphere-controls-system</code> 的项目权限。进入该项目后,通过「应用负载」的「服务」页面即可找到命名为 <code>kubesphere-router-&lt;project-namespace&gt;</code> 形式且外部访问已开放 NodePort 的网关服务。NodePort 服务端口需要通过「编辑 YAML」来直接修改。</p> <p><img src="https://static.apiseven.com/202108/1638256523468-408ee36f-aac7-4bb4-9cd3-2473a95a52f4.png" alt="指定 NodePort 端口" referrerpolicy="no-referrer"></p> <h2>开始使用集群网关</h2> <blockquote> <p>KubeSphere 3.2.0 开始支持集群级别的全局网关,所有项目可共用同一个网关,之前已创建的项目网关也不会受到集群网关的影响。也可以统一纳管所有项目的网关,对其进行集中管理和配置,管理员用户再也不需要切换到不同的企业空间中去配置网关了。</p> </blockquote> <p>如果您使用的是是 KubeSphere 3.2.0 版本,我们更推荐大家使用集群网关的功能来统一整个集群的应用路由。要启用集群网关也非常简单:使用具备集群管理权限的账号,进入其可管理的某个集群(如我们这里以 <code>default</code> 集群为例),在「集群设置」的「网关设置」中即可「开启网关」,同时查看「项目网关」。</p> <p><img src="https://static.apiseven.com/202108/1638256574546-920473f3-e8ac-4cf9-932b-4202888e7a54.png" alt="开启网关设置" referrerpolicy="no-referrer"></p> <p>集群网关开启的方式以及对齐 NodePort 访问端口的修改和之前项目网关的操作基本完全一致,这里就不多赘述了。</p> <p>但有一点需要特别注意:集群网关开启后,已经开启的项目网关还会保留;但尚未创建网关的项目是无法再创建单独的网关的,会直接使用集群网关。</p> <p>下图展示了已创建网关的项目,在同时拥有项目及集群网关后,在「网关设置」页面所呈现的所有网关概览。</p> <p><img src="https://static.apiseven.com/202108/1638256658706-ac5107fe-2fd7-4521-b830-9ae1fdf762e1.png" alt="网关设置页面一览" referrerpolicy="no-referrer"></p> <h2>快速使用 Apache APISIX Ingress Controller</h2> <p>Apache APISIX 是一款开源的高性能、动态云原生网关,由深圳支流科技有限公司于 2019 年捐赠给 Apache 基金会,目前已成为 Apache 基金会的顶级开源项目,也是 GitHub 上最活跃的网关项目。Apache APISIX 目前已覆盖 API 网关、LB、Kubernetes Ingress、Service Mesh 等多种场景。</p> <h3>如何部署</h3> <p>首先添加 Apache APISIX Helm Chart 仓库。之后选定一个企业空间,通过「应用管理」下的「应用仓库」来添加如下一个 <a href="https://charts.apiseven.com/">Apache APISIX 的仓库</a>。</p> <p><img src="https://static.apiseven.com/202108/1638256788584-dca2d21b-3ffc-4bb4-bd73-56dedb6d005a.png" alt="添加仓库" referrerpolicy="no-referrer"></p> <p>接下来创建一个名为 <code>apisix-system</code> 的项目。进入项目页面后,选择在「应用负载」中创建「应用」的方式来部署 Apache APISIX,并选择 <code>apisix</code> 应用模版开始进行部署。</p> <p><img src="https://static.apiseven.com/202108/1638241691528-80090ab6-85de-401f-96d7-58118b3cbd88.png" alt="部署应用负载" referrerpolicy="no-referrer"></p> <blockquote> <p>为何是直接部署 Apache APISIX 应用的 Helm Chart,而不是直接部署 Apache APISIX Ingress Controller?</p> </blockquote> <p>这是因为 Apache APISIX Ingress Controller 目前和 Apache APISIX 网关是强关联的(如下图所示),且目前通过 Apache APISIX Helm Charts 同时部署 Apache APISIX Gateway + Dashboard + Ingress Controller 是最方便的,因此本文推荐直接使用 Apache APISIX 的 Helm Chart 进行整套组件的部署。</p> <p><img src="https://static.apiseven.com/202108/1638241693072-9b3146f5-bcc6-4441-b002-f1a07603a8c4.png" alt="为什么直接部署 APISIX" referrerpolicy="no-referrer"></p> <p>将应用命名为 <code>apisix</code> 以避免多个组件(Gateway, Dashboard, Ingress Controller)的工作负载及服务名称产生不匹配的情况;在安装步骤中编辑的「应用设置」的部分,请参照以下配置进行填写(请特别注意带有【注意】标记的注释部分的说明,其余可以按需自行编辑修改)。</p> <pre><code class="language-yaml">global: imagePullSecrets: [] apisix: enabled: true customLuaSharedDicts: [] image: repository: apache/apisix pullPolicy: IfNotPresent tag: 2.10.1-alpine replicaCount: 1 podAnnotations: {} podSecurityContext: {} securityContext: {} resources: {} nodeSelector: {} tolerations: [] affinity: {} podAntiAffinity: enabled: false nameOverride: '' fullnameOverride: '' gateway: type: NodePort externalTrafficPolicy: Cluster http: enabled: true servicePort: 80 containerPort: 9080 tls: enabled: false servicePort: 443 containerPort: 9443 existingCASecret: '' certCAFilename: '' http2: enabled: true stream: enabled: false only: false tcp: [] udp: [] ingress: enabled: false annotations: {} hosts: - host: apisix.local paths: [] tls: [] admin: enabled: true type: ClusterIP externalIPs: [] port: 9180 servicePort: 9180 cors: true credentials: admin: edd1c9f034335f136f87ad84b625c8f1 viewer: 4054f7cf07e344346cd3f287985e76a2 allow: ipList: - 0.0.0.0/0 plugins: - api-breaker - authz-keycloak - basic-auth - batch-requests - consumer-restriction - cors - echo - fault-injection - grpc-transcode - hmac-auth - http-logger - ip-restriction - ua-restriction - jwt-auth - kafka-logger - key-auth - limit-conn - limit-count - limit-req - node-status - openid-connect - authz-casbin - prometheus - proxy-cache - proxy-mirror - proxy-rewrite - redirect - referer-restriction - request-id - request-validation - response-rewrite - serverless-post-function - serverless-pre-function - sls-logger - syslog - tcp-logger - udp-logger - uri-blocker - wolf-rbac - zipkin - traffic-split - gzip - real-ip #【注意】添加此插件以配合 Dashboard 展示服务信息 - server-info stream_plugins: - mqtt-proxy - ip-restriction - limit-conn customPlugins: enabled: true luaPath: /opts/custom_plugins/?.lua #【注意】如下配置保障 Prometheus 插件可对外暴露指标 plugins: - name: prometheus attrs: export_addr: ip: 0.0.0.0 port: 9091 configMap: name: prometheus mounts: [] dns: resolvers: - 127.0.0.1 - 172.20.0.10 - 114.114.114.114 - 223.5.5.5 - 1.1.1.1 - 8.8.8.8 validity: 30 timeout: 5 autoscaling: enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 80 targetMemoryUtilizationPercentage: 80 configurationSnippet: main: '' httpStart: '' httpEnd: '' httpSrv: '' httpAdmin: '' stream: '' etcd: enabled: true host: - 'http://etcd.host:2379' prefix: /apisix timeout: 30 auth: rbac: enabled: false user: '' password: '' tls: enabled: false existingSecret: '' certFilename: '' certKeyFilename: '' verify: true service: port: 2379 replicaCount: 3 dashboard: enabled: true #【注意】为 Dashboard 开启 NodePort 方便后续使用 service: type: NodePort ingress-controller: enabled: true config: apisix: #【注意】一定要设置 gateway 所在的 namespace serviceNamespace: apisix-system serviceMonitor: enabled: true namespace: 'apisix-system' interval: 15s </code></pre> <p>部署成功后,点击应用名称进入详情页面,可以在「资源状态」标签页下看到如下的服务部署和工作状态运行状态展示。</p> <p><img src="https://static.apiseven.com/202108/1638241694605-7d88f095-fef5-43f4-9752-8dc5a2f9abc4.png" alt="工作状态运行展示" referrerpolicy="no-referrer"></p> <blockquote> <p>Apache APISIX 项目另有的两个 Helm Chart 对应的默认配置参数可以分别参考:<a href="https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix-dashboard/values.yaml">Dashboard</a> 和 Ingress Controller 的 values.yaml。</p> </blockquote> <h3>Dashboard 妙用</h3> <p>Apache APISIX 应用部署完成后,可通过 Apache APISIX Dashboard 来检验一下 Apache APISIX 网关的当前状态。</p> <p>从应用负载-服务页面可以找到 <code>apisix-dashboard</code> 服务,由于我们在应用配置中已为 Dashboard 开启了 NodePort,所以这里可以直接通过 NodePort 端口来访问 Dashboard。</p> <p><img src="https://static.apiseven.com/202108/1638241699353-8d54dfe9-8439-4085-8e7d-02583a1d0d9e.png" alt="Dashboard 页面设置" referrerpolicy="no-referrer"></p> <p>使用默认用户名及密码 <code>admin</code> 登录 Apache APISIX Dashboard,可以进入「系统信息」页面查看到当前连接管理的「Apache APISIX 节点」信息。</p> <p><img src="https://static.apiseven.com/202108/1638241703083-0915a427-9aab-41e6-8c76-be60d70fc135.png" alt="登陆 Dashboard" referrerpolicy="no-referrer"></p> <h3>如何使用</h3> <p>接下来让我们回到「应用路由」页面,再新建一个路由(如 <code>apisix-httpbin</code>),设置路径为 <code>/*</code> <code>httpbin</code> <code>80</code> 并为其添加 <code>kubernetes.io/ingress.class</code>: <code>apisix</code> 的键值。</p> <p><img src="https://static.apiseven.com/202108/1638241705123-6fe3ba11-bc08-4fb2-a8b1-73066ce73679.png" alt="新建路由" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1638241706790-3989c06d-c803-4c16-869a-6fa000b5744b.png" alt="详细数据设置" referrerpolicy="no-referrer"></p> <h4>验证应用路由生效</h4> <p>回到 Apache APISIX Dashboard 进入「路由」页面,可以看到新建的应用路由已被 Apache APISIX Ingress Controller 识别后自动添加到了 Apache APISIX 网关中,在「上游」页面也可以看到自动创建的一个上游条目。</p> <p><img src="https://static.apiseven.com/202108/1638241712811-db1f93dd-2963-4034-b461-26733d173bae.png" alt="验证生效" referrerpolicy="no-referrer"></p> <p>接下来回到 <code>apisix-system</code> 项目「服务」页面,找到 <code>apisix-gateway</code> 服务对应的端口,由此访问 <code>&lt;apisix-httpbin 应用路由指定的域名&gt;:&lt;apisix-gateway 外部访问端口&gt;</code>(例如此处为 <code>httpbin.ui:30408</code>)即可访问到 <code>apisix-httpbin</code> 应用路由所关联的后台服务。</p> <p><img src="https://static.apiseven.com/202108/1638241716159-134e6bd8-9e08-46de-8d46-39142c439b8f.png" alt="生效成功" referrerpolicy="no-referrer"></p> <h2>自定义监控 Apache APISIX 网关</h2> <p>使用 Apache APISIX 网关时可通过 Prometheus 插件以及 KubeSphere 自带的自定义监控能力来进行监控能力的加持。</p> <h3>暴露相关 Prometheus 监控指标</h3> <p>由于我们在前边部署 Apache APISIX 应用时已经开启了 <a href="https://apisix.apache.org/docs/apisix/plugins/prometheus">Prometheus 插件</a>,所以接下来只需要把 Prometheus 监控指标的接口暴露出来即可。</p> <p>进入 <code>apisix-system</code> 项目,在「工作负载」页面找到 apisix 并进入部署详情页面,随后在左侧操作面板的「更多操作」中选择「编辑设置」。</p> <p><img src="https://static.apiseven.com/202108/1638241718162-86d110b6-2c40-461c-9cf4-a13b73cf5768.png" alt="部署工作负载" referrerpolicy="no-referrer"></p> <p>在弹出的面板中,进入到 <code>apisix</code> 容器编辑界面,找到「端口设置」,添加一个新的名为 <code>prom</code> 的端口映射到容器的 <code>9091</code> 端口,保存后 <code>apisix</code> 工作负载会重启。</p> <p><img src="https://static.apiseven.com/202108/1638241721050-c3e9409c-4ec8-4ff1-bcf8-045ea57ec179.png" alt="新建映射端口" referrerpolicy="no-referrer"></p> <h3>为监控指标创建 ServiceMonitor</h3> <p>接下来我们需要将已暴露的指标接口接入到 KubeSphere 自带的 Prometheus 中使之可被访问(被抓取指标数据)。</p> <p>由于 KubeSphere 是通过 <a href="https://github.com/prometheus-operator/prometheus-operator">Prometheus Operator</a> 来维护内部 Prometheus 系统,所以最快捷的方式自然是直接创建 ServiceMonitor 资源来实现指标接口的接入。</p> <pre><code class="language-yaml">apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: apisix namespace: apisix-system spec: endpoints: - scheme: http #【注意】使用上一步中工作负载暴露的容器端口名称 targetPort: prom #【注意】需要正确绑定 apisix 对应的指标接口路径 path: /apisix/prometheus/metrics interval: 15s namespaceSelector: matchNames: - apisix-system selector: matchLabels: app.kubernetes.io/name: apisix app.kubernetes.io/version: 2.10.0 helm.sh/chart: apisix-0.7.2 </code></pre> <p>使用 <code>kubectl apply -f your_service_monitor.yaml</code> 创建 ServiceMonitor 资源。创建成功后,如果有集群管理权限,也可以在集群的 CRD 管理页面中搜索查看 ServiceMonitor 资源并找到名为 <code>apisix</code> 的自定义资源,也可以在这里做后续的 YAML 修改。</p> <p><img src="https://static.apiseven.com/202108/1638241723331-64cb363e-b6af-4af4-93f3-29a79c9a5e77.png" alt="创建 ServiceMonitor 资源" referrerpolicy="no-referrer"></p> <h3>指标接入自定义监控面板</h3> <p>在项目左侧菜单列表中找到「监控告警」中的「自定义监控」,开始「创建」自定义监控面板。</p> <p><img src="https://static.apiseven.com/202108/1638241724906-d9531809-4682-49b3-b90b-d7f3a03e70e3.png" alt="创建自定义监控面板" referrerpolicy="no-referrer"></p> <p>在弹出窗口中填入「名称」,选择「自定义」监控模版,并进入「下一步」的监控面板创建。</p> <p><img src="https://static.apiseven.com/202108/1638241727938-cd3843f9-0e22-4316-91d2-84b56cd66f21.png" alt="设置细节" referrerpolicy="no-referrer"></p> <p>进入编辑页面后现在左侧点击 <code>+</code> 区域,在右侧的「数据」区域进行 Prometheus 监控指标的配置。例如这里我们可以用 <code>sum(apisix_nginx_http_current_connections)</code> 来统计 Apache APISIX 网关实时的连接总数。</p> <p><img src="https://static.apiseven.com/202108/1638241729416-3d2024f1-9586-44ac-ad6c-7472c8924fc8.png" alt="加入监控指标配置" referrerpolicy="no-referrer"></p> <p>保存后在页面右下角找到「+ 添加监控项」,并选择「折线图」创建 <code>Nginx connection state</code> 指标:使用 <code>sum(apisix_nginx_http_current_connections) by (state)</code> 作为指标、<code>{{state}}</code> 用作图例名称、「图例类型」为堆叠图,即可得到类似下图的结果。保存模版后即可得到第一个自定义监控面板!</p> <p><img src="https://static.apiseven.com/202108/1638241730747-298fe17e-fb34-4da6-ac9d-8b1efde4521c.png" alt="自定义监控面板生成" referrerpolicy="no-referrer"></p> <blockquote> <p>Apache APISIX 网关目前提供的 Prometheus 指标可以参见官方文档的<a href="https://apisix.apache.org/zh/docs/apisix/plugins/prometheus/#%25E5%258F%25AF%25E6%259C%2589%25E7%259A%2584%25E6%258C%2587%25E6%25A0%2587">可有的指标</a>部分。</p> </blockquote> <p>由于指标配置过程比较麻烦,推荐在集群层面的「自定义监控」中直接导入 <a href="https://grafana.com/grafana/dashboards/11719">Apache APISIX Grafana 模版</a>(下载 JSON 并通过「本地上传」进行导入)。</p> <p><img src="https://static.apiseven.com/202108/1638241733535-168ce86b-6654-4278-941d-23fb44003c90.png" alt="导入 Grafana 模版" referrerpolicy="no-referrer"></p> <p>创建完成后可直接呈现出非常丰富的 Apache APISIX 网关监控面板。KubeSphere 也同时在<a href="https://github.com/kubesphere/kubesphere/issues/4433">积极推进</a>将 Grafana 模版导入的功能引入到项目的自定义监控能力中去,敬请期待!</p> <p><img src="https://static.apiseven.com/202108/1638241735167-4c6d3a9a-8190-41b5-9e89-7f09384c7113.png" alt="完美结束" referrerpolicy="no-referrer"></p> <h2>总结</h2> <p>通过本文非常详细的步骤介绍,大家可充分了解并跟随上手体验到如何「将 Apache APISIX Ingress 网关介入 KubeSphere 并进行自定义监控」。希望通过阅读本文,可以加深各位对 Apache APISIX Ingress Controller 与 Apache APISIX 应用理解。</p> <p>作者张海立,驭势科技云平台研发总监。开源爱好者,云原生社区上海站 PMC 成员,KubeSphere Ambassador。</p>

azure-functions 插件发布,Apache APISIX 支持 Azure Functions 集成

<p><img src="https://static.apiseven.com/202108/1638431191799-e1202fc7-d3b5-48db-a222-0c70a8b70da0.png" alt="Apache APISIX 支持 Azure Functions 集成" referrerpolicy="no-referrer"></p> <p>Apache APISIX 为 Microsoft Azure Functions 提供了对 serverless 框架的支持。Apache APISIX 建议定义一个启用了无服务器插件的路由,而不是在应用程序中采用硬编码函数 URL。它使开发者能够灵活地热更新函数 URI。此外,因为 Apache APISIX 有非常强大的认证支持,这种方法还可以减轻应用逻辑中的授权和认证问题,可以用来识别和授权客户消费者访问带有 FAAS 的特定路由。本文介绍了 Apache APISIX 最近新增的插件 <code>azure-functions</code>,并详细说明了如何将 Azure Functions(一种广泛使用的 serverless 解决方案)集成到 Apache APISIX 中。</p> <h2>azure-functions 插件工作原理</h2> <p><code>azure-functions</code> 插件让用户为网关 URI 定义一个上游的 azure <code>HTTP Trigger</code> serverless 功能。如果启用,该插件将终止正在进行的对该 URI 的请求,并代表客户向 azure FAAS(新的上游)发起一个新的请求,其中包括用户设置的合适的授权细节、请求头、请求体、参数(这三个部分都是从原始请求中传递的),并将响应体、状态码和头返回给向 Apache APISIX 代理发出请求的原始客户。</p> <p>该插件支持通过 API 密钥和 azure active directory 对 azure FAAS 服务进行授权。</p> <h2>如何在 Apache APISIX 中使用 azure-functions 插件</h2> <p>该插件的主要目标是将路由配置中指定的网关路由代理到 azure function URI 上。本节为您介绍如何在 azure 云上配置和创建 serverless HTTP Trigger。</p> <ol> <li> <p>首先进入 Azure 并设置一个试用计划,最多可免费调用 100 万次。要了解更多关于定价的情况,请访问<a href="https://azure.microsoft.com/en-us/services/functions/#pricing">这里</a>。</p> </li> <li> <p>访问<a href="https://portal.azure.com/#home">Azure Portal</a>。</p> <ol> <li>首先,创建一个资源组,为 FAAS 创建逻辑分区。 <img src="https://static.apiseven.com/202108/1638349069240-911b8640-2de6-4f82-b75b-fb937b0bad40.png" alt="创建资源组" referrerpolicy="no-referrer"></li> <li>用你选择的 URL 创建一个 function 应用。 <img src="https://static.apiseven.com/202108/1638349121520-01abe8e6-bc09-4be7-b010-f7baec59f89a.png" alt="创建一个 function 应用" referrerpolicy="no-referrer"></li> </ol> </li> <li> <p>在 VSCode 编辑器中安装 <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions">Azure Functions 插件</a>。安装后,通过插件认证,并安装 azure function core tool,用于本地开发。</p> <pre><code class="language-shell">npm install -g azure-functions-core-tools@3 --unsafe-perm true </code></pre> </li> <li> <p>将下面的代码段部署到我们刚才通过 VSCode 中的 Azure Functions 扩展面板创建的同一个function 应用中。</p> <pre><code class="language-javascript">module.exports = async function (context, req) { context.log('HTTP trigger invoked on Test-APISIX.') const name = req.query.name || (req.body &amp;&amp; req.body.name) const responseMessage = name ? 'Hello, ' + name : 'This HTTP triggered function executed successfully. Pass a name in the query string or in the request body to generate a personalized response.' context.res = { // status: 200, /* Defaults to 200 */ body: responseMessage, } } </code></pre> </li> </ol> <blockquote> <p>这个代码段从查询参数中获取用户名字(如果不存在,则从请求体中获取)并向用户问好。</p> </blockquote> <h3>启用 azure-functions 插件</h3> <p>下面我们将通过一个示例为大家说明如何为一个特定的路由启用 <code>azure-functions</code> 插件。我们假设你的 HTTP Trigger 已经部署并准备好提供服务。</p> <pre><code class="language-shell"># enable plugin for a specific route curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "plugins": { "azure-functions": { "function_uri": "http://test-apisix.azurewebsites.net/api/HttpTrigger", "authorization": { "apikey": "&lt;Generated API key to access the Azure-Function&gt;" } } }, "uri": "/azure" }' </code></pre> <p>现在,任何对 Apache APISIX 网关上的 URI <code>/azure</code> 的请求(HTTP/1.1、HTTPS、HTTP2)都将触发对上述函数 URI 的 HTTP 调用,响应体与响应头和响应代码将被代理回给客户端。例如:</p> <pre><code class="language-shell">curl -i -XGET http://localhost:9080/azure\?name=Bisakh HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Request-Context: appId=cid-v1:38aae829-293b-43c2-82c6-fa94aec0a071 Date: Wed, 19 Nov 2021 18:46:55 GMT Server: APISIX/2.10.2 Hello, Bisakh </code></pre> <p>考虑到,Apache APISIX 也是在<a href="https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L26">config-default.yaml</a>上以 <code>enable_http2: true</code> 运行,端口为 9081,客户端和 Apache APISIX 代理之间的任何 <code>HTTP/2</code> 通信将被代理到 azure faas,类似于 HTTP/1.1,响应将被代理回给客户端,并有适当的标题,例如:</p> <pre><code class="language-shell">curl -i -XGET --http2 --http2-prior-knowledge http://localhost:9081/azure\?name=Bisakh HTTP/2 200 content-type: text/plain; charset=utf-8 request-context: appId=cid-v1:38aae829-293b-43c2-82c6-fa94aec0a071 Date: Wed, 19 Nov 2021 18:46:56 GMT server: APISIX/2.10.2 Hello, Bisakh </code></pre> <h3>停用 azure-functions 插件</h3> <p>如果需要停用 azure-functions 该插件,只需在插件配置中删除相应的 JSON 配置,禁用<code>azure-functions</code>插件,并添加合适的上游配置。Apache APISIX 插件是热加载的,因此不需要重新启动 Apache APISIX。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/azure", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' </code></pre> <h2>自定义配置</h2> <p>在启用 <code>azure-functions</code> 插件创建新路由时,在最小的配置中,<code>function_uri</code> 是插件配置的强制性属性,指向函数的 URL。有很多额外的选项,可以通过插件参数和元数据参数进行调整。</p> <h3>插件参数解释</h3> <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>必填</th> <th>默认值</th> <th>有效值</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>function_uri</td> <td>string</td> <td>是</td> <td>n/a</td> <td>n/a</td> <td>触发 serverless functions 代码的 azure functions Endpoint(例如:http://test-apisix.azurewebsites.net/api/HttpTrigger)。</td> </tr> <tr> <td>authorization</td> <td>object</td> <td>否</td> <td>n/a</td> <td>n/a</td> <td>访问云 functions 的授权凭证。</td> </tr> <tr> <td>authorization.apikey</td> <td>string</td> <td>否</td> <td>n/a</td> <td>n/a</td> <td>授权内的字段。生成API密钥来授权对该端点的请求。</td> </tr> <tr> <td>authorization.clientid</td> <td>string</td> <td>否</td> <td>n/a</td> <td>n/a</td> <td>授权内的字段。客户端ID(azure active directory),用于授权对该端点的请求。</td> </tr> <tr> <td>timeout</td> <td>integer</td> <td>否</td> <td>3000</td> <td>[100,...]</td> <td>代理请求超时,以毫秒为单位。</td> </tr> <tr> <td>ssl_verify</td> <td>boolean</td> <td>否</td> <td>true</td> <td>true/false</td> <td>是否启用执行服务器的SSL验证。</td> </tr> <tr> <td>keepalive</td> <td>boolean</td> <td>否</td> <td>true</td> <td>true/false</td> <td>是否重复使用同一个代理连接。设置为false则禁用keepalives并立即关闭连接。</td> </tr> <tr> <td>keepalive_pool</td> <td>integer</td> <td>否</td> <td>5</td> <td>[1,...]</td> <td>池中的最大连接数。</td> </tr> <tr> <td>keepalive_timeout</td> <td>integer</td> <td>否</td> <td>60000</td> <td>[1000,...]</td> <td>最大的空闲超时,以毫秒为单位。</td> </tr> </tbody> </table> <p>这为严格约束 azure FAAS 的行为提供了很大的灵活性--从配置超时到 keepalive 池以及验证无服务器FAAS 的 SSL 证书。说实话,当涉及到无服务器时,这实际上意味着很多,因为服务是事件驱动的,而且资源是由云提供商即时分配的。</p> <h3>Metadata 参数解释</h3> <p>同样,有一些属性可以通过使用元数据进行调整。</p> <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>必填</th> <th>默认值</th> <th>有效值</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>master_apikey</td> <td>string</td> <td>否</td> <td>""</td> <td>n/a</td> <td>可用于访问 azure functions URI 的 API KEY。</td> </tr> <tr> <td>master_clientid</td> <td>string</td> <td>否</td> <td>""</td> <td>n/a</td> <td>可用于授权 function URI的客户ID(active directory)。</td> </tr> </tbody> </table> <p><code>azure-functions</code> 插件的元数据提供了授权回退的功能。它定义了 <code>master_apikey</code> 和 <code>master_clientid</code> (azure active directory client id),用户可以为关键任务的应用部署定义主 API 密钥或客户端 ID。因此,如果在插件属性中没有找到授权细节,元数据中的授权细节就会启动。</p> <p>优先级排序如下</p> <ul> <li> <p>首先,该插件在 Apache APISIX 代理的请求头中寻找 <code>x-functions-key</code> 或 <code>x-functions-clientid</code> 键。</p> </li> <li> <p>如果没有找到,azure-functions 插件会检查插件属性中的授权细节。如果存在,它会将相应的标头添加到发送到 Azure cloud function 的请求中。</p> </li> <li> <p>如果在插件属性中没有找到授权细节,Apache APISIX 将为该插件获取元数据配置并使用主密钥。</p> </li> </ul> <p>要添加一个新的主 APIKEY,请用更新的元数据向 <code>/apisix/admin/plugin_metadata</code> 端点提出请求,如下所示:</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/azure-functions \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "master_apikey" : "&lt;Your azure master access key&gt;" }' </code></pre> <h2>总结</h2> <p><code>azure-functions</code> 插件是 Apache APISIX 为 serverless 设计的第二个插件。我们正在开发其他 serverless 插件,并会在即将发布的 Apache APISIX 版本中介绍这些插件。如果大家感兴趣,请<a href="https://github.com/apache/apisix/issues/new/choose">提交 Issue</a>来分享你的意见,也可以在我们的<a href="https://apisix.apache.org/docs/general/community">邮件列表</a>中分享开发新插件的建议!</p>

如何在 Kubernetes 集群中使用 Nocalhost 开发 Apache APISIX Ingress Controller

<h2>环境准备</h2> <ul> <li>准备一个可用的 Kubernetes 集群。可使用任意拥有命名空间管理权限的 Kubernetes 集群</li> <li>本地已安装好 <a href="https://helm.sh/">Helm v3.0+</a></li> <li>集群中已安装好 Apache APISIX</li> <li>GoLand IDE 2020.03+ (本文应用的是 2021.2 版本)</li> <li>安装 <a href="https://nocalhost.dev/zh-CN/docs/installation#install-jetbrains-plugin">Nocalhost JetBrains plugin</a> 插件</li> <li>安装 <a href="https://golang.org/dl/">Go 1.13</a> 或更高版本</li> </ul> <h2>部署 Apache APISIX Ingress Controller</h2> <p>在 GoLand 中通过 Nocalhost 部署 Apache APISIX Ingress Controller,操作如下:</p> <ol> <li> <p>在 GoLand 中打开 Nocalhost 插件</p> </li> <li> <p>选择将要部署 APISIX Ingress Controller 的命名空间</p> </li> <li> <p>右键点击选定的命名空间,选择 <strong><code>Deploy Application</code></strong>,然后选择 <strong><code>Helm Repo</code></strong> 作为安装方法</p> </li> <li> <p>在下面的对话框中:</p> <ol> <li>在 <code>Name</code> 中输入:<code>apisix-ingress-controller</code></li> <li>在 <code>Chart URL</code> 中输入:<code>https://charts.apiseven.com</code></li> </ol> </li> </ol> <p><img src="https://static.apiseven.com/202108/1637131316244-f1a58c88-8628-4918-a4c4-1ad287742fd0.gif" alt="部署 APISIX ingress controller" referrerpolicy="no-referrer"></p> <p>部署完成后,我们通过在 IDE 内启用端口转发来测试 <code>apisix-ingress-controller</code>:</p> <ol> <li>在 Nocalhost 插件的 Workloads 中找到 <code>apisix-ingress-controller</code>,右键点击并选择 <strong><code>Port Forward</code></strong></li> <li>添加端口转发 <code>8080:8080</code></li> <li>在本地访问 <a href="http://127.0.0.1:8080/healthz"><code>http://127.0.0.1:8080/healthz</code></a> 并检查结果</li> </ol> <p><img src="https://static.apiseven.com/202108/1637131450462-842c3baf-b7a4-4598-be0b-27486bf1cf28.gif" alt="测试部署是否成功" referrerpolicy="no-referrer"></p> <h2>开发</h2> <h3>步骤一:进入开发模式</h3> <ol> <li>右键点击 <code>apisix-ingress-controller</code> 工作负载,选择 <strong><code>Start DevMode</code></strong></li> <li>如果您已经将源码克隆到本地,请选择您的源代码目录。否则通过输入仓库地址 https://github.com/apache/apisix-ingress-controller.git 来让 Nocalhost 克隆你的源代码到本地</li> <li>等待操作完成,Nocalhost 将在进入 DevMode 后在 IDE 内打开远程终端</li> </ol> <p>现在通过在远程终端中输入以下命令来启动 <code>apisix-ingress-controller</code> 进程:</p> <pre><code class="language-bash">go run main.go ingress --config-path conf/config-default.yaml </code></pre> <p><code>apisix-ingress-controller</code> 启动后,可通过 <a href="http://127.0.0.1:8080/healthz"><code>http://127.0.0.1:8080/healthz</code></a> 访问服务,并检查结果。</p> <p><img src="https://static.apiseven.com/202108/1637131513751-b9184c10-4da3-4ab2-b403-56ae2360704a.gif" alt="进入开发模式" referrerpolicy="no-referrer"></p> <h3>步骤二:修改代码并检查结果</h3> <p>现在我们来修改一下代码并看看效果:</p> <ol> <li>停止 <code>apisix-ingress-controller</code> 进程</li> <li>在 Goland 中搜索 <code>healthz</code> 并找到 <code>router.go</code> 文件。将 <code>healthzResponse</code> 的状态代码从 <code>ok</code> 更改为 <code>Hello Nocalhost</code></li> <li>重新启动进程并在本地检查更改结果</li> </ol> <p><img src="https://static.apiseven.com/202108/1637131699629-a0766f66-0faa-4bf8-9013-284e5f2bdd57.gif" alt="无需重新构建镜像或重启容器,几秒后便可以看到改动的结果" referrerpolicy="no-referrer"></p> <h3>步骤三:结束开发模式</h3> <p>现在关闭开发窗口并退出开发模式:</p> <ol> <li>右键点击 <code>apisix-ingress-controller</code></li> <li>选择 <strong><code>End DevMode</code></strong></li> </ol> <p>Nocalhost 将使 <code>apisix-ingress-controller</code> 结束开发模式, 并重置 <code>apisix-ingress-controller</code> 到其原始版本。启用端口转发来看看退出开发模式后的结果。</p> <p><img src="https://static.apiseven.com/202108/1637131766524-dba7b756-ae0b-42d1-8ff0-6ac14059ce11.gif" alt="结束 DevMode" referrerpolicy="no-referrer"></p> <p>注意:在开发模式下修改代码时,所有代码更改都只在<strong>开发容器</strong>中生效。</p> <p>退出开发模式后,Nocalhost 将会将远程容器重置为原始状态(进入开发模式之前的版本)。这样,在退出开发模式后,对代码进行修改不会对原始环境造成任何更改或影响。</p> <h2>调试</h2> <p>调试应用程序是一件麻烦的事,在 Kubernetes 集群中调试应用程序则更加麻烦。但 Nocalhost 可以帮助我们在调试 Kubernetes 集群中的程序时获得和在 IDE 中直接调试本地程序同样的体验。</p> <h3>步骤一:开启远程调试</h3> <p>我们可以通过以下方式开启远程调试</p> <ul> <li>右键点击 <code>apisix-ingress-controller</code> 并选择 <strong><code>Remote Debug</code></strong></li> <li>Nocalhost 将会先让 <code>apisix-ingress-controller</code> 进入开发模式,并运行在 <a href="https://nocalhost.dev/zh-CN/docs/config/config-develop"><code>dev config</code></a> 定义的调试命令</li> </ul> <p><img src="https://static.apiseven.com/202108/1637132327260-7bba1d81-cf70-4982-9a07-51cc379e6bea.gif" alt="开始远程调试" referrerpolicy="no-referrer"></p> <h3>步骤二:设置断点</h3> <p>在 <code>healthz</code> 函数上设置一个断点。悬停在行号左侧,然后点击红点。设置好断点后,在浏览器中访问 <a href="http://127.0.0.1:8080/healthz"><code>http://127.0.0.1:8080/healthz</code></a>,会触发断点,GoLand 会跳到前台。点击调试相关按钮可对程序进行调试。</p> <p>此外,因为我们启用了 <code>dev.hotReload</code>,所以每次更改代码时,Nocalhost 将自动重新运行调试命令。这可以让我们频繁更改和调试代码时变得方便很多。</p> <p><img src="https://static.apiseven.com/202108/1637132455552-86f44c0c-94d1-4ad9-a79d-0e2c6957d60b.gif" alt="设置断点并运行服务" referrerpolicy="no-referrer"></p> <h2>远程运行</h2> <p>Nocalhost 不仅仅可以远程调试,还为我们在 Kubernetes 集群中运行服务以及热加载提供了一种更简单的方式。</p> <p>我们可以通过以下步骤使用 Remote Run 功能:</p> <ul> <li>右键点击 <code>apisix-ingress-controller</code>,并选择 <strong><code>Remote Run</code></strong></li> <li>Nocalhost 将会先让 <code>apisix-ingress-controller</code> 进入开发模式,并运行在 <a href="https://nocalhost.dev/zh-CN/docs/config/config-develop"><code>dev config</code></a> 定义的运行命令</li> </ul> <p>每次更改代码完代码后,Nocalhost 都会自动触发运行命令,将程序运行起来。</p> <p><img src="https://static.apiseven.com/202108/1637133046432-84810667-c3ee-4d71-8a33-eb1833fd9ce2.gif" alt="Remote run" referrerpolicy="no-referrer"></p> <h2>总结</h2> <p>通过本文,我们为大家展示了如何使用 Nocalhost 来开发和调试 Kubernetes 集群中的 Apache APISIX Ingress Controller。借助 Nocalhost 的能力,我们不再需要等待缓慢的本地开发过程,而是可以通过即时反馈和高效的云本地开发环境进行快速部署与迭代。</p>

重磅功能!Apache APISIX 拥抱 WASM 生态

<p>在即将发布的 Apache APISIX 版本(2.11.0)中,我们新增了对于 WASM 的支持!现在开发者除了可以使用 Lua、Java、Go、Python、JavaScript 等多种编程语言开发 APISIX 的插件之外,也可以用 WASM 来开发插件。</p> <p><img src="https://static.apiseven.com/202108/1637289637179-ab74d38f-acd4-4401-908f-e1d310a33583.png" alt="拥抱 WASM 生态" referrerpolicy="no-referrer"></p> <p>WASM 全称 <a href="https://webassembly.org/">WebAssembly</a>,与上述具体编程语言运行时的不同之处在于,它是一套字节码标准,专门设计成可以在宿主环境中嵌套使用。</p> <p>如果某种编程语言提供编译成 WASM 字节码的功能,就可以把该语言的应用编译成 WASM 字节码,运行在某个支持 WASM 的宿主环境中。</p> <p>听起来,是不是只要某个宿主环境支持 WASM,就能像操作系统一样运行任意应用呢?</p> <p>但其实这里有个限制,就像操作系统需要实现特定标准的 syscall 一样,要想运行特定的应用,也需要实现该应用所需的 API。</p> <p>以 JavaScript 为例,虽然同样是 JavaScript 代码,但是针对浏览器写的 JS 模块不能直接用在 npm 包里面,因为两个的 API 不一样。</p> <p>所以仅仅把 WASM 放到 Apache APISIX 里面并行不通,要想让开发者在 Apache APISIX 上运行 WASM,我们还需要提供一套专门的 API。</p> <h2>为什么选择 Proxy WASM</h2> <p>对于如何提供这套 API,我们曾经权衡过两套方案:</p> <ol> <li>参考 lua-nginx-module 的接口,实现对应的 WASM 版 API</li> <li>实现 Proxy WASM 这一套标准</li> </ol> <p><a href="https://github.com/proxy-wasm/spec">Proxy WASM</a> 是 Envoy 的 WASM API 标准。所以上述问题其实等价于,我们是自己搞一套 API 标准还是复用 Envoy 已有标准呢?</p> <p>WASM API 标准可以拆成两个方面来看:</p> <ol> <li>Host,负责提供 API 的实现</li> <li>SDK,要想在不同的编程语言里面调用提供的 API,需要使用该语言来实现一套胶水层</li> </ol> <p>如果我们遵循 Envoy 的标准,优势在于可以复用 Envoy 现有的 WASM SDK(Proxy WASM SDK),而不足之处在于这套标准是 Envoy 结合自己情况制定的,如果我们跟着实现,没有自己量身定制那么轻松。</p> <p>经过社区的讨论后,我们最终决定采用 Proxy WASM 标准。「做难且正确的事」,实现 Proxy WASM 自然是难的事,但我们相信这是正确的事,通过社区的合作和共建,可以构建更加繁荣的生态。</p> <h2>如何在 Apache APISIX 中使用 WASM</h2> <p>Apache APISIX 目前已初步支持 WASM,可以使用 WASM 来编写 fault-injection 插件的部分功能。感兴趣的读者可以在本月底的 Apache APISIX 2.11.0 版本中尝尝鲜,敬请期待!</p> <p>下面我们将结合 <a href="https://github.com/tetratelabs/proxy-wasm-go-sdk/">proxy-wasm-go-sdk</a> 来讲讲怎么用 WASM 实现注入自定义响应的功能。</p> <h3>步骤一:基于 proxy-wasm-go-sdk 编写代码</h3> <p>实现代码(包含 <code>go.mod</code> 和其他)具体细节可<a href="https://github.com/apache/apisix/tree/master/t/wasm">点击此处</a>查阅。</p> <p>这里需要解释下,虽然 proxy-wasm-go-sdk 这个项目带了 Go 的名字,但它其实用的是 tinygo 而不是原生的 Go。因为原生的 Go 在支持 WASI (你可以认为它是非浏览器的 WASM 运行时接口)时会有一些问题,详情可<a href="https://github.com/tetratelabs/proxy-wasm-go-sdk/blob/main/doc/OVERVIEW.md#tinygo-vs-the-official-go-compiler">点击此处</a>查阅。</p> <h3>步骤二:构建对应的 WASM 文件</h3> <pre><code class="language-shell">tinygo build -o ./fault-injection/main.go.wasm -scheduler=none -target=wasi ./fault-injection/main.go </code></pre> <h3>步骤三:在 Apache APISIX 的 config.yaml 引用该文件</h3> <pre><code class="language-yaml">apisix: ... wasm: plugins: - name: wasm_fault_injection priority: 7997 file: t/wasm/fault-injection/main.go.wasm </code></pre> <p>通过以上操作,你可以像用 Lua 插件一样用这个 WASM 插件,比如:</p> <pre><code class="language-yaml">uri: "/wasm" plugins: wasm_fault_injection: conf: '{"body":"hello world", "http_status":200}' upstream: type: roundrobin nodes: 127.0.0.1:1980: 1 </code></pre> <p>注意 WASM 插件的配置都是 conf 字段下面的一条字符串,由对应的插件自己去做解析。</p> <h2>横向测评——条条大道通罗马</h2> <p>Apache APISIX 发展到现在,已经有三种编写插件的方式:</p> <ol> <li>原生的 Lua way,跑在 APISIX 里面</li> <li>多种语言的外部插件 runner,插件逻辑跑在 APISIX 外面</li> <li>把多种语言编译成 WASM,依然跑在 APISIX 里面</li> </ol> <p><img src="https://static.apiseven.com/202108/1637289637159-f2fd1f09-4be6-4cd4-88a0-9c3a23c4f405.png" alt="APISIX 生态支持" referrerpolicy="no-referrer"></p> <p>这三种方式在诸如生态、成熟度等各个方面都差异很大。正巧我们都可以用它们来实现 fault-injection,所以可以比比看。</p> <h3>步骤一:配置路由</h3> <p>Lua way 的 fault-injection,自然是使用内置的 fault-injection 插件。Runner way 的 fault-injection 实现具体可<a href="https://github.com/apache/apisix-go-plugin-runner/blob/master/cmd/go-runner/plugins/fault_injection.go">点击此处</a>查阅。</p> <p>接下来让我们分别给它们配置不同的路由:</p> <pre><code class="language-yaml">--- uri: "/wasm" plugins: wasm_fault_injection: conf: '{"body":"hello world", "http_status":200}' upstream: type: roundrobin nodes: 127.0.0.1:1980: 1 --- plugins: ext-plugin-pre-req: conf: - name: fault-injection value: '{"body":"hello world", "http_status":200}' upstream: nodes: 127.0.0.1:1980: 1 type: roundrobin uri: /ext-plugin --- plugins: fault-injection: abort: body: hello world http_status: 200 upstream: nodes: 127.0.0.1:1980: 1 type: roundrobin uri: /fault-injection </code></pre> <h3>步骤二:实际压测</h3> <p>接下来试着用 wrk 进行压测,具体数据对比如下:</p> <p><img src="https://static.apiseven.com/202108/1637289637162-6d2ef1d6-9de8-410c-8ca6-e264205c1be1.png" alt="压测结果对比" referrerpolicy="no-referrer"></p> <p>从上述结果可以看到,WASM 版本的性能介于外部插件和原生 Lua 之间。</p> <p>WASM 版本的性能之所以比外部插件好那么多,是因为 fault-injection 功能简单,所以外部插件 RPC 带来的性能损耗过于明显。考虑到我们还没有对 WASM 实现做任何优化,这种情况已经让我们感到满意了。</p> <p>而 WASM 的另一个好处,就是让我们一下子拥有多语言的支持(这也托了 Proxy WASM SDK 的福)。具体细节可参考下方文档:</p> <ul> <li><a href="https://gist.github.com/spacewander/0357198ea21e022003c407fd23155f79">Rust 版本 fault-injection</a></li> <li><a href="https://gist.github.com/spacewander/64773a706f1dc758aecc7f28aff7555d">AssemblyScript 版本 fault-injection</a></li> </ul> <h2>道路曲折,但前途光明</h2> <p>说了这么多 WASM 的好处,是不是有点心动呢?但它目前并非是一个完美的解决方案, WASM/Proxy WASM 还是有一些不够成熟的地方。比如:</p> <ul> <li><strong>编程语言支持待完善</strong>:原生 Go 的 WASM 支持,主要是基于浏览器环境的,所以我们不得不用 tinygo 来实现。但是 tinygo 作为一个年轻的项目,还是有不少局限性。</li> <li><strong>Proxy WASM 生态有待成熟</strong>:AssemblyScript 版本的 fault injection 实现,并没有 JSON decode 的部分。这是因为 AssemblyScript SDK 是基于 AssemblyScript 0.14.x 版本实现的,而几个开源的 AssemblyScript JSON 库都是针对高版本 AssemblyScript 实现的,没办法用在较为陈旧的 AssemblyScript 0.14 上。</li> <li><strong>WASM 没有内置协程</strong>:WASM 目前尚未内置协程,所以没办法很好地被宿主的调度系统给调度起来。</li> </ul> <p>虽然上面列举了几点不足之处,但是我们相信这个技术栈的前景是光明的:</p> <ol> <li>包括 Apache APISIX 和 Envoy 等开源项目对于 WASM 都很看重,有许多初创公司和大企业为 WASM 生态添砖加瓦,这意味着诸如 AssemblyScript SDK 停滞不前这样的困难,只会是暂时。长久看,Proxy WASM 的生态会枝繁叶茂。</li> <li>WASM 作为 serverless 和 edge computing 的宠儿,有着光明的未来。在众多实际场景的落地和优化,会更快的解决技术上的不足。</li> </ol> <h2>写在最后</h2> <p>Apache APISIX 是个紧跟技术潮流的项目,“好风凭借力,送我上青天”,Apache APISIX 支持 WASM 是个长期的过程。</p> <p>“千里之行,始于足下”,Apache APISIX 为了支持 WASM,已经发起了 <a href="https://github.com/api7/wasm-nginx-module">wasm-nginx-module</a> 这个开源项目。感兴趣的读者可以关注该项目的进展,“独行者速,众行者远”,期待你的加入,一起创造世界顶级项目。</p>

如何与 Dapr 集成打造 Apache APISIX 网关控制器

<p>本质上,Apache APISIX 控制器将配置相同标准 Dapr annotations 以注入 daprd sidecar。通过公开这个 sidecar,将允许外部应用程序与集群中启用 Dapr 的应用程序进行通信。</p> <p>下图为实际项目中的架构流程:</p> <p><img src="https://static.apiseven.com/202108/1637119221118-75dab9f1-4092-4684-ad23-34932d8a7eac.png" alt="总体架构流程" referrerpolicy="no-referrer"></p> <h2>基本项目概览</h2> <h3>Apache APISIX Ingress</h3> <p>在 K8s 生态中,Ingress 作为表示 K8s 流量入口的一种资源,想要让其生效,就需要有一个 Ingress Controller 去监听 K8s 中的 Ingress 资源,并对这些资源进行相应规则的解析和实际承载流量。在当下趋势中,像 Kubernetes Ingress Nginx 就是使用最广泛的 Ingress Controller 实现。</p> <p>而 APISIX Ingress 则是另一种 Ingress Controller 的实现。跟 Kubernetes Ingress Nginx 的区别主要在于 APISIX Ingress 是以 Apache APISIX 作为实际承载业务流量的数据面。如下图所示,当用户请求到具体的某一个服务/API/网页时,通过外部代理将整个业务流量/用户请求传输到 K8s 集群,然后经过 APISIX Ingress 进行后续处理。</p> <p><img src="https://static.apiseven.com/202108/1637119221119-71bbe219-dd19-46be-90fb-20cd667d9805.png" alt="APISIX Ingress" referrerpolicy="no-referrer"></p> <p>从上图可以看到,APISIX Ingress 分成了两部分。一部分是 APISIX Ingress Controller,作为控制面它将完成配置管理与分发。另一部分 APISIX Proxy Pod 负责承载业务流量,它是通过 CRD(Custom Resource Definitions) 的方式实现的。Apache APISIX Ingress 除了支持自定义资源外,还支持原生的 K8s Ingress 资源。</p> <p>点击查看<a href="https://www.apiseven.com/blog/apisix-ingress-details">更多详情</a>。</p> <h3>Dapr</h3> <p>Dapr 是一个可移植、事件驱动的运行时。它使开发人员简单地去构建运行在云和 edge 上弹性、无状态和有状态的应用,并且包含多种语言和开发人员框架。</p> <p><img src="https://static.apiseven.com/202108/1637119221120-15a5be20-17a2-4c18-a82e-91e1ff3709f0.png" alt="Dapr 生态图" referrerpolicy="no-referrer"></p> <p>今天,我们正经历一波云应用浪潮。开发人员熟悉 web+ 数据库应用程序架构(例如经典的 3 层设计),但不熟悉本质上是分布式的微服务应用程序架构。开发人员希望专注于业务逻辑,同时依靠平台为他们的应用程序注入伸缩性、弹性、可维护性、弹性和其他本地云架构的属性。</p> <p>这就是 Dapr 的用武之地。</p> <p>Dapr 可以将构建微服务应用程序的最佳实践编入开放、独立的构建块中,使用户能够使用自己选择的语言和框架构建可移植的应用程序。每个构建块都是完全独立,并可在应用程序中使用其中的一个或多个。</p> <p>此外,Dapr 与平台无关,这意味着用户可以在任何 Kubernetes 集群和其他与 Dapr 集成的托管环境本地运行应用程序。</p> <p>点击查看<a href="https://docs.dapr.io/zh-hans/concepts/overview/">更多详情</a>。</p> <h2>实践开始</h2> <h3>环境准备</h3> <ul> <li>Kubernetes 1.19+ 集群,集群上已经配置了 Dapr</li> <li>安装了 Helm CLI 3x</li> <li>Kubectl CLI 已安装并配置为访问集群</li> <li>可选:用于创建自签名证书的 OpenSSL</li> <li>Apache APISIX 的 Helm Chart 版本为 0.7.2+</li> </ul> <h3>步骤一:Apache APISIX Helm 配置</h3> <p>通过运行以下命令为 Apache APISIX 控制器添加最新的 helm chart repo:</p> <pre><code class="language-shell">helm repo add apisix https://charts.apiseven.com helm repo update </code></pre> <h3>步骤二:创建 Apache APISIX Ingerss 命名空间</h3> <p>确保当前 kubectl 上下文指向正确的 Kubernetes 集群,然后运行以下命令:</p> <pre><code class="language-shell">kubectl create namespace ingress-apisix </code></pre> <h3>步骤三:安装支持 Dapr 的 APISIX 控制器</h3> <p>使用以下内容创建一个名为 dapr-annotations.yaml 的文件,以在 Apache APISIX Proxy Pod 上设置注释。</p> <pre><code class="language-yaml">apisix: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: " apisix-gateway" dapr.io/app-port: "9080" dapr.io/enable-metrics: "true" dapr.io/metrics-port: "9099" dapr.io/sidecar-listen-addresses: 0.0.0.0 dapr.io/config: ingress-apisix-config </code></pre> <blockquote> <p>注意:上面的 app-port 是告诉 daprd sidecar Proxy 在监听哪个端口。有关受支持的注释完整列表,可参考 <a href="https://docs.dapr.io/operations/hosting/kubernetes/kubernetes-annotations/">Dapr Kubernetes pod 注释规范</a>。</p> </blockquote> <p>下面以我个人在 AKS 上安装的示例 dapr-annotations.yaml 进行展示。</p> <pre><code class="language-yaml"> apisix: podAnnotations: dapr.io/app-id: apisix-gateway dapr.io/app-port: '9080' dapr.io/enable-metrics: 'true' dapr.io/enabled: 'true' dapr.io/metrics-port: '9099' dapr.io/sidecar-listen-addresses: 0.0.0.0 dapr.io/config: ingress-apisix-config gateway: type: LoadBalancer ingress-controller: enabled: true dashboard: enabled: true </code></pre> <p>接下来运行以下命令(引用上述文件):</p> <pre><code class="language-shell">helm install apisix apisix/apisix -f dapr-annotations.yaml -n ingress-apisix </code></pre> <h3>步骤四:创建 Apache APISIX 的 Dapr Sidecar 资源</h3> <p>首先,配置 Apache APISIX upstream-apisix-dapr。</p> <p><img src="https://static.apiseven.com/202108/1637119221106-e57ae8b8-38ed-46ea-b219-401619fadbe3.png" alt="配置上游业务" referrerpolicy="no-referrer"></p> <p>在这里主机名填写:apisix-gateway-dapr,端口号填写 3500。</p> <pre><code class="language-json">{ "nodes": [ { "host": "apisix-gateway-dapr", "port": 3500, "weight": 1 } ], "retries": 1, "timeout": { "connect": 6, "read": 6, "send": 6 }, "type": "roundrobin", "scheme": "http", "pass_host": "pass", "name": "apisix-dapr" } </code></pre> <p>然后配置 Apache APISIX 服务 apisix-gateway-dapr,上游服务选择 apisix-dapr。</p> <p><img src="https://static.apiseven.com/202108/1637119221115-ae7c847a-99a3-4ee6-b36f-4269fd067198.png" alt="配置服务" referrerpolicy="no-referrer"></p> <pre><code class="language-json">{ "name": "apisix-gateway-dapr", "upstream_id": "376187148778341098" } </code></pre> <h3>步骤五:部署测试示例项目</h3> <p><a href="https://httpbin.org/">HTTPBin</a> 是以 Python+Flask 写的一款工具,这款工具涵盖了各类 HTTP 场景,且每个接口都有返回。接下来,我们使用 kennethreitz/httpbin 作为示例项目进行演示。</p> <pre><code class="language-shell">kubectl apply -f 01.namespace.yaml kubectl apply -f 02.deployment.yaml kubectl apply -f 03.svc.yaml </code></pre> <p><img src="https://static.apiseven.com/202108/1637119221110-9b901451-6ca6-4d15-b591-69f7c5d57ce1.png" alt="项目配置" referrerpolicy="no-referrer"></p> <p>上图为假设有一个使用 Dapr app-id kennethreitz-httpbin 运行的微服务。</p> <h4>路径匹配改写</h4> <p>这里补充一下关于路径匹配的相关设置。比如请求网关是 /httpbin/<em>,后端接收路径应该是 /</em>,中间的 httpbin 只充当服务名的标识。</p> <p><img src="https://static.apiseven.com/202108/1637119221114-90c1ef58-6743-419c-be87-9cdc8503aa30.png" alt="填写释义" referrerpolicy="no-referrer"></p> <p>在支持命名空间的托管平台上,Dapr 应用 ID 是符合有效的 FQDN 格式,其中包括目标名称空间。例如,以下字符串包含应用 ID (svc-kennethreitz-httpbin) 以及应用运行在命名空间(kind-test)。</p> <p>最后可以通过访问:http://20.195.90.43/httpbin/get 来查看代理是否成功。</p> <p><img src="https://static.apiseven.com/202108/1637119221100-13997340-dfb6-45fb-abba-4215e0318238.png" alt="检查是否代理成功" referrerpolicy="no-referrer"></p> <h2>额外补充说明</h2> <p>当然,在进行部署的过程中,也可以在 Kubernetes 中使用 Apache APISIX 官方 Helm 仓库直接部署 Apache APISIX 和 APISIX Ingress Controller。这样可以直接将 Apache APISIX 作为网关,进行 APISIX Ingress Controller 的数据面来承载业务流量。</p> <p>最后将 Dapr 通过 Sidecar annotations 注入到 Apache APISIX Proxy Pod,通过服务调用模块来调用集群中的微服务,实现完整流程部署。</p> <h3>删除 Apache APISIX 控制器</h3> <p>如项目结束,想要删除 Apache APISIX 控制器,可按下方命令操作(记得不要忘记删除之前创建的命名空间 ingress-apisix)。</p> <pre><code class="language-shell">helm delete apisix -n ingress-apisix </code></pre>

APISIX-Datadog 插件发布,助力用户提高系统的可观测性

<p><img src="https://static.apiseven.com/202108/1636955062917-28911d71-0d56-48ec-85e5-a7908195da2f.png" alt="APISIX-Datadog 插件封面图" referrerpolicy="no-referrer"></p> <p>随着应用开发的复杂度增加,监控成为了应用的一个重要组成部分。及时、准确的监控既能满足快速迭代的周期性需求,又能够确保应用的稳定性和流畅性。如何选择一个适合的监控,以提升应用的可观测性,成为了每个开发者都必须面临的一道难题。</p> <p><a href="http://apisix.apache.org/">Apache APISIX</a> 将监控和可观测性从应用中解耦了出来,给开发人员带来了一个优势:在构建应用的时候,只需要关注业务逻辑,而 Apache APISIX 可以和开发人员选定的监控平台对接,处理可观测性的问题。</p> <p>Apache APISIX 最近发布了一个新的插件:APISIX-Datadog,以提供与 Datadog 监控平台的集成。这篇文章介绍了 APISIX-Datadog 插件的实现原理及功能。</p> <h2>APISIX-Datadog plugin 工作原理</h2> <p><img src="https://static.apiseven.com/202108/1636685752757-d02d8305-2a68-4b3e-b2cc-9e5410c8bf11.png" alt="APISIX-Datadog 插件架构图" referrerpolicy="no-referrer"></p> <p>APISIX-Datadog 插件将其自定义指标推送到 DogStatsD server。而 DogStatsD server 通过 UDP 连接与 Datadog agent 捆绑在一起。DogStatsD 是 StatsD 协议的一个实现。它为 Apache APISIX agent 收集自定义指标,将其聚合成一个数据点,并将其发送到配置的 Datadog server。要了解更多关于 DogStatsD 的信息,请访问 DogStatsD 文档。</p> <p>当你启用 APISIX-Datadog 插件时,Apache APISIX agent 会在每个请求响应周期向 DogStatsD server 输出以下指标:</p> <table> <thead> <tr> <th>参数名称</th> <th>StatsD 类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>Request Counter</td> <td>Counter</td> <td>收到的请求数量。</td> </tr> <tr> <td>Request Latency</td> <td>Histogram</td> <td>处理该请求所需的时间,以毫秒为单位。</td> </tr> <tr> <td>Upstream latency</td> <td>Histogram</td> <td>上游 server agent 请求到收到响应所需的时间,以毫秒为单位。</td> </tr> <tr> <td>APISIX Latency</td> <td>Histogram</td> <td>APISIX agent 处理该请求的时间,以毫秒为单位。</td> </tr> <tr> <td>Ingress Size</td> <td>Timer</td> <td>请求体大小,以字节为单位。</td> </tr> <tr> <td>Egress Size</td> <td>Timer</td> <td>响应体大小,以字节为单位。</td> </tr> </tbody> </table> <p>这些指标将被发送到 DogStatsD agent,并带有以下标签。如果任何特定的标签没有合适的值,该标签将被直接省略。</p> <table> <thead> <tr> <th>参数名称</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>route_name</td> <td>路由的名称,如果不存在,将显示路由 ID。</td> </tr> <tr> <td>service_id</td> <td>如果一个路由是用服务的抽象概念创建的,那么特定的服务 ID 将被使用。</td> </tr> <tr> <td>consumer</td> <td>如果路由有一个链接的消费者,消费者的用户名将被添加为一个标签。</td> </tr> <tr> <td>balancer_ip</td> <td>处理了当前请求的上游复制均衡器的的 IP。</td> </tr> <tr> <td>response_status</td> <td>HTTP 响应状态代码。</td> </tr> <tr> <td>scheme</td> <td>已用于提出请求的协议,如 HTTP、gRPC、gRPCs 等。</td> </tr> </tbody> </table> <p>APISIX-Datadog 插件维护了一个带有 timer 的 buffer。当 timer 失效时,APISIX-Datadog 插件会将 buffer 的指标作为一个批量处理程序传送给本地运行的 DogStatsD server。这种方法通过重复使用相同的 UDP 套接字,对资源的占用较少,而且由于可以配置 timer,所以不会一直让网络过载。</p> <h2>启动 Datadog Agent</h2> <ol> <li> <p>如果你已经在使用 Datadog,你必须在系统中安装一个 Datadog agent。它可以是一个 docker 容器,一个 pod 或二进制的包管理器。你只需要确保Apache APISIX agent 可以到达 Datadog agent 的 8125 端口。</p> <blockquote> <p>如果需要了解更多关于如何安装一个完整的 Datadog agent,请访问<a href="https://docs.datadoghq.com/agent/">这里</a>。</p> </blockquote> </li> <li> <p>如果你从没使用过 Datadog</p> <ol> <li>首先访问 www.datadoghq.com ,创建一个账户。</li> <li>然后按照下图标注的步骤生成 API 密钥。 <img src="https://static.apiseven.com/202108/1636685007445-05f134fd-e80a-4173-b1d7-f0a118087998.png" alt="Generate an API Key" referrerpolicy="no-referrer"></li> </ol> </li> <li> <p>APISIX-Datadog 插件只需要依赖 <code>datadog/agent</code> 的 dogstatsd 组件即可实现,因为该插件按照 statsd 协议通过标准的 UDP 套接字向 DogStatsD server 异步发送参数。我们推荐使用独立的 <code>datadog/dogstatsd</code> 镜像,而不是使用完整的<code>datadog/agent</code> ,因为 <code>datadog/dogstatsd</code> 的组件大小只有大约 11 MB,更加轻量化。而完整的 <code>datadog/agent</code> 镜像的大小为 2.8 GB。</p> </li> </ol> <p>运行以下命令,将它作为一个容器来运行:</p> <pre><code class="language-shell"># pull the latest image docker pull datadog/dogstatsd:latest # run a detached container docker run -d --name dogstatsd-agent -e DD_API_KEY=&lt;Your API Key from step 2&gt; -p 8125:8125/udp datadog/dogstatsd </code></pre> <p>如果你在生产环境中使用 Kubernetes,你可以将 <code>dogstatsd</code> 作为一个 <code>Daemonset</code> 或 <code>Multi-Container Pod</code> 与 Apache APISIX agent 一起部署。</p> <h2>如何在 Apache APISIX 中使用 Datadog</h2> <h3>启用 APISIX-Datadog 插件</h3> <p>如果你已经启动了 <code>dogstatsd</code> agent,只需执行一条命令,就可以为指定路由启用 APISIX-Datadog 插件。</p> <pre><code class="language-shell"># enable plugin for a specific route curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "plugins": { "datadog": {} }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } }, "uri": "/hello" }' </code></pre> <p>启用 APISIX-Datadog 插件后,任何对端点 URI <code>/hello</code> 的请求都会产生上述指标,并推送到 Datadog agent的本地 DogStatsD server。</p> <h3>自定义配置</h3> <p>如果启用 APISIX-Datadog 插件时,使用的是默认配置,dogstatsd 服务在 <code>127.0.0.1:8125</code> 可用。如果你想更新配置,请更新插件的元数据。</p> <h4>元数据参数解释</h4> <table> <thead> <tr> <th>参数名称</th> <th>类型</th> <th>是否必须修改</th> <th>默认值</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>hosts</td> <td>string</td> <td>否</td> <td>"127.0.0.1"</td> <td>DogStatsD server 的主机地址</td> </tr> <tr> <td>port</td> <td>integer</td> <td>否</td> <td>8125</td> <td>DogStatsD server的主机端口</td> </tr> <tr> <td>namespace</td> <td>string</td> <td>否</td> <td>"apisix"</td> <td>由 APISIX agent发送的所有自定义参数的前缀。对寻找指标图的实体很有帮助,例如:(apisix.request.counter)。</td> </tr> <tr> <td>constant_tags</td> <td>array</td> <td>否</td> <td>["source:apisix"]</td> <td>静态标签嵌入到生成的指标中。对某些信号的 metrics 进行分组很有用。</td> </tr> </tbody> </table> <p>要了解更多关于如何有效地编写标签,请访问<a href="https://docs.datadoghq.com/getting_started/tagging/#defining-tags">这里</a>。</p> <p>向 /apisix/admin/plugin_metadata 端点发出请求,更新元数据,如下所示。</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/datadog -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "host": "127.0.0.1", "port": 8125, "constant_tags": [ "source:apisix", "service:custom" ], "namespace": "apisix" }' </code></pre> <h4>插件参数解释</h4> <p>与元数据类似,在启用 APISIX-Datadog 插件时,你也可以调整其他参数。</p> <p>|参数名称|类型|是否必须修改|默认值|取值范围||描述| |----|----|--------|-------|-----|-----------| |batch_max_size|integer|否|5000|[1,...]|每个批次的 buffer 最大值| |inactive_timeout|integer|否|5|[1,...]|如果不活跃,buffer 将被刷新的最长时间(秒)。| |buffer_duration|integer|否|60|[1,...]|在必须处理一个批次之前,该批次中最老的条目的最长存活时间(秒)。| |max_retry_count|integer|否|1|[1,...]|如果一个条目未能到达 dogstatsd server,重试的次数。|</p> <p>由于所有的字段都是可选的,如果你没有手动设置任何参数,APISIX-Datadog 插件将使用默认值设置这些参数。如果你需要更新任何参数,只需用更新的参数值更新所需的路由、服务或消费者。例如,下面的代码将 <code>batch_max_size</code> 修改为 10。</p> <pre><code class="language-shell">'{ ... "plugins": { "datadog": { "batch_max_size": 10 } } ... }' </code></pre> <h3>停用 APISIX-Datadog 插件</h3> <p>你只需在插件配置中删除相应的 json 配置即可停用 APISIX-Datadog 插件。得益于 Apache APISIX 插件的热加载机制,删除 json 配置后,会立即停用APISIX-Datadog 插件,不需要重新加载。</p> <pre><code class="language-shell"># disable plugin for a route curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/hello", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' </code></pre>

浅谈 Apache APISIX 的可观测性

<p>可观测性是从系统外部去观察系统内部程序的的运行时状态和资源使用情况。衡量可观测性的主要手段包括:Metrics、Logging 和 Tracing,下图是 Metrics、Logging 和 Tracing 之间的关系。</p> <p><img src="https://static.apiseven.com/202108/1635993536337-f8ee034d-ef3b-40b6-9886-ebde62d8edc6.png" alt="Metrics、Logging 和 Tracing 关系图" referrerpolicy="no-referrer"></p> <p>举个例子,Tracing 和 Logging 重合的部分代表的是 Tracing 在 request 级别产生的日志,并通过 Tracing ID 将 Tracing 和 Logging 关联起来。对这份日志进行一定的聚合运算之后,能够得到一些 Metrics。Tracing 自身也会产生一些 Metrics,例如调用量之间的关系。</p> <h2>Apache APISIX 的可观测性能力</h2> <p>Apache APISIX 拥有完善的可观测性能力:支持 Tracing 和 Metrics、拥有丰富的 Logging 插件生态、支持查询节点状态。</p> <h3>Tracing</h3> <p>Apache APISIX 支持多种 Tracing 插件,包括:Zipkin、OpenTracing 和 SkyWalking。需要注意是: Tracing 插件默认处于关闭状态,使用前需要手动开启 Tracing 插件;Tracing 插件需要与路由或全局规则绑定,如果没有采样率的要求,建议与全局规则绑定,这样可以避免遗漏。</p> <h3>Metrics</h3> <p>在 Apache APISIX 中, Metrics 的相关信息通过 Prometheus Exporter上报,兼容 Prometheus 的数据格式。在 Apache APISIX 中使用 Prometheus Plugin 有两件事情需要注意。</p> <p><strong>第一,请尽量提高路由、服务和上游这三者名称的可读性。</strong></p> <p>Prometheus Plugin 中有一个名为 <code>prefer_name</code> 的参数,将这个参数的值设置为 <code>true</code> 时,即:<code>prefer_name: true</code>,如果路由、服务和上游这三者的名称可读性比较强,这会带来一些好处:后续通过 Grafana 监控大屏展示参数的时候,不仅能够清楚地展示出所有的数据,还能够清晰地知晓这些数据的来源。如果 <code>prefer_name</code> 参数的值为 <code>false</code>,则只会展示资源的 ID 作为数据来源,例如 路由 ID、上游 ID,进而造成监控大屏的可读性较低的问题。</p> <p><strong>第二,Prometheus Plugin 必须与路由或者全局规则绑定,然后才可以查看到指定资源的 Metrics。</strong></p> <p>完成上述设置以后,Metrics 的数据会存储在 Prometheus 里面。由于 Prometheus 的存储性能很好,但展示性能欠佳,所以我们需要借助 Grafana Dashboard 展示数据。我们可以看到 Nginx 实例的 Metrics、网络带宽的 Metrics、路由和上游的 Metrics 等,详情如下图所示:</p> <p><img src="https://static.apiseven.com/202108/1635993660940-9c9bbb0b-d5f1-4add-b93d-1f076de9aebd.png" alt="Grafana Dashboard" referrerpolicy="no-referrer"></p> <h3>Logging</h3> <p>Apache APISIX 支持多种日志插件,可以与其他外部的平台直接分享日志数据。Error Log 插件支持 HTTP 与 TCP 协议,并且兼容 SkyWalking 的日志格式。也可以通过 FluentBit 等日志收集组件,将日志同步到日志平台进行处理。</p> <p>Access Log 插件目前还不支持在日志格式里面进行嵌套。因为 Access Log 插件是路由级别的,所以需要跟路由进行绑定,才可以收集到路由的访问日志。但是日志的格式是全局的,而全局只能有一份日志格式。</p> <h3>支持查询节点状态</h3> <p>Apache APISIX 的支持查询节点状态,启用之后,可以通过 <code>/apisix/status</code> 收集到节点的信息,包括节点数、等待链接数、处理连接数等。</p> <p><img src="https://static.apiseven.com/202108/1635993774170-ca3bf15d-9f55-42ac-9a2f-2d8955f74c5c.png" alt="节点状态" referrerpolicy="no-referrer"></p> <h3>美中不足</h3> <p>上文讲到,Apache APISIX 的可观测性能力非常完善,能够收集 Metrics、Logging 和 Tracing 等信息。虽然借助 Apache APISIX 的内置插件配合 Grafana Dashboard,能够解决监控数据收集和指标可视化问题,但是各种数据分散在各个平台。期望有一个可观测性分析平台能集成 Metrics、Logging、Tracing 信息,能够将所有数据联动起来。</p> <h2>使用 Apache SkyWalking 增强 Apache APISIX 的观测能力</h2> <p>Apache SkyWalking 是一个针对分布式系统的应用性能监控(APM)和可观测性分析平台。它提供了多维度应用性能分析手段,从分布式拓扑图到应用性能指标、Trace、日志的关联分析与告警。</p> <p><img src="https://static.apiseven.com/202108/1635993914263-b7511acd-9bcf-49ca-aa32-911fc85acfac.png" alt="Apache SkyWalking" referrerpolicy="no-referrer"></p> <h3>一站式数据处理</h3> <p>Apache SkyWalking 支持对接 Metrics、Logging、Tracing 等多种监控数据,兼容 Prometheus 数据模型,还可以通过 Log Analysis Language 进行二次聚合,产生新的 Metrics。</p> <h3>更详细的数据展示</h3> <p>Apache SkyWalking 的 Dashboard 分为上下两个区域。上部是功能选择区域,下部是面板内容。Dashboard 提供全局、服务、示例、Endpoint 等多个实体维度的 Metrics 相关信息,支持以不同的视图展示可观测性。以全局视图为例,展示的 Metrics 包括:服务负载、慢服务数量、不健康的服务数量等,如下图所示。</p> <p><img src="https://static.apiseven.com/202108/1635993968588-403c9219-ae66-4b97-9eee-dcb97067b789.png" alt="数据展示" referrerpolicy="no-referrer"></p> <p>另外值得一说的是 SkyWalking Dashboard 的 Trace 视图。SkyWalking 提供了 3 种展现形式:列表、树状图和表格。Trace 视图是分布式追踪的典型视图,这些视图允许用户从不同角度查看追踪数据,特别是 Span 间的耗时关系。</p> <p>SkyWalking Dashboard 也支持拓扑图。拓扑图是根据探针上行数据分析出的整体拓扑结构。拓扑图支持点击展现和下钻单个服务的性能统计、Tracing、告警,也可以点击拓扑图中的关系线,展示服务之间、服务示例间的性能 Metrics。</p> <h3>支持容器化部署</h3> <p>Kubernetes 是一个开源的云原生容器化集群管理平台,目标是让部署容器化的应用简单且高效。Apache SkyWalking 后台可以部署在 Kubernetes 之中,而且得益于 Kubernetes 的高效率管理,可以保证 UI 组件的高可用性。</p> <p>如果在集群上部署了 Apache APISIX,Apache SkyWalking 支持以 sidecar 或服务发现的形式部署 SkyWalking Satellite,监控集群中的 Apache APISIX。</p> <h2>未来计划</h2> <p>Apache APISIX 在未来仍会继续加强可观测性相关的功能支持,例如:</p> <ol> <li> <p>解决 SkyWalking Nginx-Lua 插件的 peer 缺失问题</p> </li> <li> <p>支持在日志中打印 trace id</p> </li> <li> <p>接入访问日志</p> </li> <li> <p>支持网关元数据</p> </li> </ol> <h2>结语</h2> <p>本文介绍了 Apache APISIX 的可观测性能力以及如何通过 Apache SkyWalking 提升Apache APISIX 的可观测性能力。未来两个社区还会继续深度合作,进一步增强 Apache APISIX 的可观测性能力。希望大家能够多多地参与到 Apache APISIX 和 Apache SkyWalking 项目中来。如果你对这两个开源项目很感兴趣,却不熟悉代码,写文章、做视频、对外分享、积极参与社区和邮件列表讨论都是很不错方式。</p>

Apache APISIX 扩展指南

<p>Apache APISIX 提供了 50 多个插件、常用的几个负载均衡选择器,以及对主流服务发现(如 Nacos 和 DNS)的支持。API 网关和企业内部的业务紧密相关,为了满足企业的业务需求,用户通常需要在 Apache APISIX 的基础上添加一些代码,以实现业务所需的功能。如何拓展 Apache APISIX 成了许多用户的共同痛点:在保证 Apache APISIX 平稳运行的前提下,如何添加业务代码,满足实际需求?</p> <p>本文提供了 Apache APISIX 的拓展指南,旨在为用户提供拓展 Apache APISIX 的一些思路。由于 Apache APISIX 处于高速发展阶段,版本迭代的频率比较高,所以本文会以 Apache APISIX 的首个 LTS 版本 v2.10.0 为基础进行说明。如果你使用的 Apache APISIX 版本低于 2.10.0,可能需要结合实际情况做一些变通。另外,虽然本文只讲解了 HTTP 相关的逻辑,但是 TCP 相关的部分,大体上也较为相似。</p> <h2>扩展方向1:Rewrite 还是 Access?</h2> <p>我们从请求的生命周期说起:当一个请求进入到 Apache APISIX 时,首先会由 <code>http_access_phase</code> 这个方法进行处理。熟悉 OpenResty phases 概念的读者可能会有些疑惑:OpenResty 一共有 6 个 phases,按照执行的先后顺序排列,分别是: <code>rewrite</code>、 <code>access</code>、 <code>before_proxy</code>、 <code>header_filter</code>、 <code>body_filter</code> 和 <code>log</code>,为什么一上来就是 <code>access</code>,<code>rewrite</code> 怎么不见了?</p> <p>Apache APISIX 插件的 phases 概念和 OpenResty 的 phases 概念略有不同。为了提升 Apache APISIX 的性能,APISIX 插件的 rewrite 方法会在 OpenResty 的 access 阶段中运行。用户依然能够在插件层面自定义 <code>rewrite</code> 的逻辑,但是在代码层面,<code>rewrite</code> 实际上也是在 <code>access</code> 里面执行。</p> <p>虽然说 <code>rewrite</code> 的逻辑和 <code>access</code> 的逻辑都在 access phase 里面运行,但是 <code>rewrite</code> 的逻辑还是会比 <code>access</code> 的逻辑先执行。为了避免后续插件执行 <code>rewrite</code> 失败后,没有继续执行 <code>access</code>,导致 trace 遗漏的问题,必须在<code>rewrite</code>里面添加 trace 的逻辑。</p> <p>除了执行的先后顺序外,<code>rewrite</code> 和 <code>access</code> 之间还有一个差别,就是它们之间有一个处理 <code>consumer</code> 的逻辑:</p> <pre><code class="language-Lua"> plugin.run_plugin("rewrite", plugins, api_ctx) if api_ctx.consumer then ... end plugin.run_plugin("access", plugins, api_ctx) </code></pre> <p><code>consumer</code> 代表一种身份。你可以针对不同的 <code>consumer</code> 进行权限控制,比如使用 <code>consumer-restriction</code> 这个插件实现基于角色的权限控制,也就是大家所说的 RBAC。另外,你也可以给不同的 <code>consumer</code> 设置对应的限流策略。</p> <p>Apache APISIX 里面的鉴权插件(在插件定义里面有 <code>type = "auth"</code>),需要在 <code>rewrite</code> 阶段选好 <code>consumer</code>。这里我们用 <code>key-auth</code> 插件举个例子:</p> <pre><code class="language-Lua">local _M = { version = 0.1, priority = 2500, type = 'auth', name = plugin_name, schema = schema, consumer_schema = consumer_schema, } ... function _M.rewrite(conf, ctx) ... local consumer_conf = consumer_mod.plugin(plugin_name) if not consumer_conf then return 401, {message = "Missing related consumer"} end local consumers = lrucache("consumers_key", consumer_conf.conf_version, create_consume_cache, consumer_conf) local consumer = consumers[key] if not consumer then return 401, {message = "Invalid API key in request"} end consumer_mod.attach_consumer(ctx, consumer, consumer_conf) end </code></pre> <p>鉴权插件的执行逻辑都是相似的:首先从用户输入中获取某组参数,然后根据参数查找对应的 <code>consumer</code>,最后连同该插件对应的 <code>consumer_conf</code> 附加到 <code>ctx</code> 中。</p> <p>综上,对于无需在请求早期阶段执行,且不需要查找 <code>consumer</code> 的插件,建议把逻辑写到 <code>access</code> 里面。</p> <h2>扩展方向2:配置服务发现</h2> <p>在执行完 <code>access</code> 之后,我们就要跟上游(Upstream)打交道了。通常情况下,上游节点是写死在 Upstream 配置上的。不过也可以从服务发现上获取节点来实现 discovery。</p> <p>接下来我们会以 Nacos 为例,讲讲怎么实现。</p> <p>一个动态获取 Nacos 管理的节点的 Upstream 配置如下:</p> <pre><code class="language-JSON">{ "service_name": "APISIX-NACOS", "type": "roundrobin", "discovery_type": "nacos", "discovery_args": { "namespace_id": "test_ns", "group_name": "test_group" } } </code></pre> <p>我们可以看到其中三个重要的变量:</p> <ol> <li><code>discovery_type</code>: 服务发现的类型,<code>"discovery_type": "nacos"</code>表示使用 Nacos 实现服务发现。</li> <li><code>service_name</code>: 服务名称。</li> <li><code>discovery_args</code>: 不同的 discovery 特定的参数,Nacos 的特定参数包括:<code>namespace_id</code> 和 <code>group_name</code>。</li> </ol> <p>Nacos discovery 对应的 Lua 代码位于 <code>discovery/nacos.lua</code>。打开 <code>nacos.lua</code> 这个文件,我们可以看到它里面实现了几个所需的方法。</p> <p>一个 discovery 需要实现至少两个方法:<code>nodes</code> 和 <code>init_worker</code> 。</p> <pre><code class="language-Lua">function _M.nodes(service_name, discovery_args) local namespace_id = discovery_args and discovery_args.namespace_id or default_namespace_id local group_name = discovery_args and discovery_args.group_name or default_group_name ... end function _M.init_worker() ... end </code></pre> <p>其中<code>nodes</code> 的函数签名已经明了地展示获取新节点所用的查询参数:<code>service_name</code> 和 <code>discovery_args</code>。每次请求时,Apache APISIX 都会用这一组查询最新的节点。该方法返回的是一个数组:</p> <pre><code class="language-Bash">{ {host = "xxx", port = 12100, weight = 100, priority = 0, metadata = ...}, # priority 和 metadata 是可选的 ... } </code></pre> <p>而 <code>init_worker</code> 负责在后台启动一个 timer,确保本地的节点数据能跟服务发现的数据保持一致。</p> <h2>扩展方向3:配置负载均衡</h2> <p>获取到一组节点后,我们要按照负载均衡的规则来决定接下来要先尝试哪个节点。如果常用的几种负载均衡算法满足不了需求,你也可以自己实现一个负载均衡。</p> <p>让我们以最少连接数负载均衡为例。对应的 Lua 代码位于 <code>balancer/least_conn.lua</code>。打开 <code>least_conn.lua</code> 这个文件,我们可以看到它里面实现了几个所需的方法:<code>new</code>、<code>get</code>、<code>after_balance</code> 和 <code>before_retry_next_priority</code>。</p> <ul> <li> <p><code>new</code> 负责做一些初始化工作。</p> </li> <li> <p><code>get</code> 负责执行选中节点的逻辑。</p> </li> <li> <p><code>after_balance</code> 在下面两种情况下会运行:</p> <ul> <li>每次重试之前(此时 before_retry 为 true)</li> <li>最后一次尝试之后</li> </ul> </li> <li> <p><code>before_retry_next_priority</code> 则是在每次尝试完当前一组同 priority 的节点,准备尝试下一组之前运行。</p> </li> </ul> <pre><code class="language-Lua">function _M.new(up_nodes, upstream) ... return { upstream = upstream, get = function (ctx) ... end, after_balance = function (ctx, before_retry) ... if not before_retry then if ctx.balancer_tried_servers then core.tablepool.release("balancer_tried_servers", ctx.balancer_tried_servers) ctx.balancer_tried_servers = nil end return nil end if not ctx.balancer_tried_servers then ctx.balancer_tried_servers = core.tablepool.fetch("balancer_tried_servers", 0, 2) end ctx.balancer_tried_servers[server] = true end, before_retry_next_priority = function (ctx) if ctx.balancer_tried_servers then core.tablepool.release("balancer_tried_servers", ctx.balancer_tried_servers) ctx.balancer_tried_servers = nil end end, } end </code></pre> <p>如果没有内部状态需要维护,可以直接借用固定的模板代码(上述代码中,位于省略号以外的内容)来填充 <code>after_balance</code> 和 <code>before_retry_next_priority</code> 这两个方法。</p> <p>选中节点之后,我们也可以通过插件的形式添加额外的逻辑。插件可以实现 <code>before_proxy</code> 方法。该方法会在选中节点之后调用,我们可以在该方法里面记录当前选中的节点信息,这在 trace 里面会有用。</p> <h2>扩展方向4:处理响应</h2> <p>我们可以通过 <code>response-rewrite</code> 插件,在<code>header_filter</code> 和 <code>body_filter</code>处理上游返回的响应。前一个方法是修改响应头,后一个方法修改响应体。注意 Apache APISIX 的响应处理是流式的,如果<code>header_filter</code>里面没有修改响应头,响应头就会被先发送出去,到了<code>body_filter</code>就没办法修改响应体了。</p> <p>这意味着如果你后续想要修改body,但是 header 里面又有 Content-Length 之类跟 body 相关的响应头,那么就要提前在 <code>header_filter</code> 里面把这些头改掉。我们提供了一个辅助方法:<code>core.response.clear_header_as_body_modified</code>,只需要在 <code>header_filter</code> 调用它就行。</p> <p><code>body_filter</code> 也是流式的,而且还会被多次调用。所以如果你想要获取完整的响应体,你需要把每次 <code>body_filter</code> 提供的部分响应体给拼起来。在 Apache APISIX master 分支上,我们提供了一个叫做 <code>core.response.hold_body_chunk</code> 的方法来简化操作,感兴趣的读者可以看看代码。</p> <h2>扩展方向5:上报日志和监控参数</h2> <p>在请求结束之后,我们还可以通过 <code>log</code> 方法来做一些清场工作。这一类工作可以分成两类:</p> <ol> <li>记录 metrics 指标,比如 <code>prometheus</code> 插件。</li> <li>记录 access log,然后定期上报,比如 <code>http-logger</code> 插件。</li> </ol> <p>如果你感兴趣的话,可以看看这两个插件的 <code>log</code> 方法是怎么实现的:</p> <ul> <li><a href="https://apisix.apache.org/zh/docs/apisix/plugins/prometheus/"><code>prometheus</code> 插件文档</a></li> <li><a href="https://apisix.apache.org/zh/docs/apisix/plugins/http-logger/"><code>http-logger</code> 插件文档</a></li> </ul>

从 0 到 1,APISIX Ingress 加入社区后的发展与收获

<h2>概念篇</h2> <h3>APISIX Ingress 概述</h3> <p>在 K8s 生态中,Ingress 作为表示 K8s 流量入口的一种资源,想要让其生效,就需要有一个 Ingress Controller 去监听 K8s 中的 Ingress 资源,并对这些资源进行相应规则的解析和实际承载流量。</p> <p>APISIX Ingress 则是基于 Apache APISIX 的 Ingress Controller 实现,实现了对 Kubernetes 的扩展,同时也支持 Ingress resource 的原生资源定义。</p> <p><img src="https://static.apiseven.com/202108/1635304156040-50b7a2ae-ed0c-42ac-8517-edd0715e0082.png" alt="APISIX Ingress 架构" referrerpolicy="no-referrer"> 通过上图可以看到,APISIX Ingress 是在 Kubernetes 集群中部署,并代理 Kubernetes 外部集群的请求。然后将这些请求反向代理到 Kubernetes 集群 Service,同时也支持直接将服务推送到 Service Pod。</p> <h3>什么是 Apache APISIX</h3> <p>前边我们提到了 APISIX Ingress 是采用 Apache APISIX 作为实际承载业务流量的数据面,那么 Apache APISIX 项目又是做什么的呢?</p> <p>Apache APISIX 是 Apache 基金会旗下的顶级开源项目,也是当前最活跃的开源网关项目,目前也通过中国信通院的可信开源项目认证。作为一个动态、实时、高性能的开源 API 网关,Apache APISIX 提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。</p> <p><img src="https://static.apiseven.com/202108/1635304156053-68751f2e-40e7-4932-99a4-5b9cc8f60628.png" alt="Apache APISIX 架构" referrerpolicy="no-referrer"></p> <p>从上图框架可以看到,Apache APISIX 分为两部分,左侧数据面用来处理流量的反向代理,右侧控制面负责配置的分发。</p> <p>Apache APISIX Ingress Controller 采用声明式的配置,经过内部处理后,最终会通过控制面的 Admin API 将数据同步到 etcd 中并传输给 Apache APISIX,实现 Apache APISIX 集群的配置同步。</p> <p>更多关于 Apache APISIX Ingress Controller 特性讲解<a href="https://github.com/apache/apisix-ingress-controller">点此查阅</a>。</p> <h2>成长篇</h2> <p>关于 Apache APISIX Ingress 的使用场景或者产品对比优势等,在往期的分享中大都提及了很多。这次我们换个角度,从 Apache APISIX Ingress 的诞生及发展角度进行解析。</p> <h3>加入 Apache 社区</h3> <p><img src="https://static.apiseven.com/202108/1635304156115-9d5e41d0-a8e9-4a6e-8a1a-757f3107a49a.png" alt="发展历程" referrerpolicy="no-referrer"></p> <p>2019 年我给 APISIX Ingress Controller 项目提供了第一行代码,2020 年 12 月份该项目被正式加入到 Apache 社区。在产品更新上,今年 6 月份我们发布了第一个 GA 版本,同时在刚刚过去的 10 月份中也发布了 1.3 版本,预计在今年 11 月份将会发布 1.4 版本,保证项目的正常更新迭代。</p> <p><img src="https://static.apiseven.com/202108/1635304156111-d0b82a61-b304-42ce-8d3a-2b959d3cb271.png" alt="贡献者增长曲线图" referrerpolicy="no-referrer"></p> <p>上图是 Apache APISIX Ingress Controller 的贡献者增长曲线图。结合时间轴可以看到,从 2020 年 12 月加入 Apache 社区后,贡献者的人数增加速度呈现出高速稳定增长的态势。侧面反映出 Apache APISIX Ingress 得到了越来越多小伙伴的关注,并开始逐步应用到企业生产环境中。</p> <h3>在社区中成长</h3> <p>从个人项目或企业内部孵化出的项目开始到加入社区,前后环境的转换必然会导致项目工作方式的变化。加入社区后,Apache APISIX Ingress 在功能和项目整体度上得到了更多的支持与帮助。</p> <h4>开启异步化讨论</h4> <p>成为 Apache 软件基金会项目后,Apache APISIX Ingress Controller 项目变得更加开放。比如关于产品每一个特性的新增或者修改都必须经过一些公开的讨论,讨论的方式一般分为邮件列表讨论和 GitHub Issue 讨论。</p> <p><img src="https://static.apiseven.com/202108/1635304156102-8877f3da-a43d-4b94-9a84-a95743546112.png" alt="邮件列表" referrerpolicy="no-referrer"> <img src="https://static.apiseven.com/202108/1635304156096-c0eeb189-54f8-4ebe-b019-f41001869186.png" alt="GitHub Issue" referrerpolicy="no-referrer"></p> <p>目前是上述两种讨论同时发起,尽可能多地让大家站在各自的使用场景以及使用角度去评判特性的合理性。因为这已不再是一个个人项目,而是一个社区项目,是多人参与的合作产出。</p> <p>同时,通过邮件列表和 GitHub Issue 的异步式讨论,可以更全面地收集到社区的反馈(不管是提问还是回答),在公开化的基础上为后续问题的搜索和整理提供了便利。</p> <h4>增设社区例会</h4> <p>在互动方面,Apache APISIX Ingress 吸取了一些其他社区的经验,开放了一个每两周进行的社区例会活动。</p> <p>这是一个新的渠道,我们希望让项目透明化的同时,也可以为社区小伙伴提供一个更生活化的渠道来一起讨论问题。</p> <p>通过这个双周例会,我们会给大家详细介绍最近两周项目发生了哪些变化,有哪些新的 issue 被提出以及哪些 PR 正在等待合并。当然也会跟大家一起讨论当前项目的一些问题或建议。</p> <p>我们希望这不仅是一个即时讨论的过程,更是一个分享和交流多角度事物观察的互动。</p> <p>具体关于双周例会的会议内容<a href="https://github.com/apache/apisix-ingress-controller/issues/614">点此查阅</a>,也可以点此查看<a href="https://space.bilibili.com/551921247">往期例会回放</a>。</p> <h4>项目细节更合规</h4> <p>进入 Apache 社区后,另一个比较大的变化是项目规划上变得更加规范,不管是代码、测试还是版本发布。</p> <p>在代码层面,目前社区采用的是 <a href="https://github.com/uber-go/guide">Golang 代码规范</a>,通过 Action CI 进行一些自动化检查。</p> <p>为了保证项目特性能够快速合并,并且不会引入新的 bug,在测试规范上我们也进行了相关要求。比如在特性开发过程中,一定要包含单元测试或者 e2e 测试,其中 e2e 测试集成了 gruntwork-io/terratest 以及 kubernetes-sigs/kind,用来构建 Kubernetes 测试环境。</p> <p>同时测试框架采用的是社区中广为接纳的 Ginkgo,测试用例的完善极大地保证了项目稳定性,同时也降低了项目的维护成本。</p> <p>在版本发布方面,目前也是严格遵循了 Apache 社区的发版规范。同时由于 APISIX Ingress Controller 也是属于 Kubernetes 的一个扩展,所以在涉及 CRD 的迭代部分也是按照 Kubernetes 的发版规则进行。</p> <h2>收获篇</h2> <p>除了上述提到的关于项目制度上的一些规范,走向社区的过程中,我们也收获了很多小伙伴们的「技术回馈」。</p> <h3>完善了更多细节功能</h3> <p>这些贡献大都来源于社区小伙伴们平时在使用 APISIX Ingress 当中遇到的一些问题,或者场景上的一些完善,比如:</p> <ul> <li>Admission Hook</li> <li>Ingress 本身的 Prometheus Metrics</li> <li>mTLs</li> <li>灰度功能的完善</li> <li>产品使用文档的补充</li> </ul> <p>更多特性<a href="https://github.com/apache/apisix-ingress-controller/pulls?q=is%253Apr">点此查看</a>。</p> <p>同时借助社区的反馈,我们也顺应大家的需求支持了<a href="https://github.com/apache/apisix-ingress-controller/blob/master/install.md#installation">多平台集成功能</a>。</p> <p><img src="https://static.apiseven.com/202108/1635304156088-035cb0b0-8138-4e93-af5c-8e6ee8371f81.png" alt="多平台集成" referrerpolicy="no-referrer"></p> <h3>丰富了使用场景库</h3> <p>在社区里得到功能加持的同时,也收获了关于 Apache APISIX Ingress 的使用场景上的丰富。</p> <h4>场景一:Kubernetes 集群内部</h4> <p>最典型的一种方式是在 Kubernetes 集群内部进行部署,如下图就是一个典型的使用场景示意图。</p> <p><img src="https://static.apiseven.com/202108/1635304156077-ced688eb-9dbf-4895-b7a2-acb2f4a288b2.png" alt="使用场景1" referrerpolicy="no-referrer"></p> <p>客户端经过外部 LB 后,经过 Apache APISIX 进行承接处理。Apache APISIX 作为网关也是一个反向代理,同时还可以部署在 Kubernetes 集群内外。</p> <p>上图的部署场景就是在 Kubernetes 内部集成 APISIX Ingress Controller,通过 APISIX Ingress Controller 将 Kubernetes 的声明式配置同步到 Apache APISIX。这样外部的请求就可以通过 Apache APISIX 集群数据面去直接代理后续 Upstream 的业务服务。</p> <h4>场景二:跨集群部署</h4> <p>苏州思必驰公司的用户为我们提供了关于跨集群使用场景,大体流程如下所示。</p> <p><img src="https://static.apiseven.com/202108/1635304156072-ae9a3943-e686-4629-a5b7-0b5c38301139.png" alt="跨集群部署" referrerpolicy="no-referrer"></p> <p>在上图架构中有两个集群,即云主机正式集群和物理机集群。Apache APISIX Ingress Controller 在每一个集群内都有部署,在与 Kubernetes API server 交互的同时,通过 Apache APISIX Admin API 将配置同步到 Apache APISIX 集群。</p> <p>在跨集群场景时,主要是通过 Apache APISIX 来打通集群之间的互相访问。通常集群之间的访问分为专线和公网,借助 Apache APISIX 的健康检查功能,可以做到当专线或公网传输失败时自动将流量切换到其他正常通道上,保证了业务的稳定与高可用。</p> <h4>场景三:一个 Apache APISIX 集群管理多个 Kubernetes 集群</h4> <p><img src="https://static.apiseven.com/202108/1635304156063-c7d879c6-8dfb-4ead-a88d-b5bdc9e453d6.png" alt="多 Kubernetes 集群" referrerpolicy="no-referrer"></p> <p>该使用场景是将 APISIX Ingress Controller 部署在 Kubernetes 集群内部,与场景一不同的是这里有多个 Kubernetes 集群。但相应的 Apache APISIX 实际上是部署在所有 Kubernetes 集群外部,然后通过 Apache APISIX Ingress Controller 将各自集群的配置同步到总的 Apache APISIX 集群中。</p> <p>这样做的优势是可以通过一套 SLB Cluster 去完全控制各个 Kubernetes 集群,满足一些企业架构为多集群或跨机房的使用场景,减少业务流量上的转发次数。</p> <h3>总结</h3> <p>得益于以上收获成果,Apache APISIX Ingress 也得到了越来越多的关注,越来越多的企业也开始将 APISIX Ingress Controller 应用到自家产品中,比如中国移动、又拍云、有赞、观为智慧等多家企业。未来期待更多企业选择 Apache APISIX Ingress。</p> <h2>未来规划</h2> <p>Apache APISIX Ingress 在不断迭代的过程中,也收到了很多社区小伙伴的一些建议,比如对未来产品的一些功能规划:</p> <h3>后续支持 Kubernetes gateway API</h3> <p>目前 Kubernetes 社区里也有很多企业在做自己的 Ingress 项目支持,Kubernetes 社区为了能统一 Ingress 的设计,给出了 gateway API 的实现标准。一旦实现了这种标准,后续用户再使用 APISIX Ingress 时,就可以做到同一份配置在不同的 Ingress 里去使用,完美适配多方部署。</p> <h3>后续支持 Ingress Controller 单体架构</h3> <p>目前社区里有一些声音会认为 Apache APISIX 所依赖的 etcd 实际上是一个有状态的服务,一旦涉及到有状态的服务,就需要额外去关注存储和迁移相关的工作。</p> <p>大家希望在容器化的云原生场景下,让 Apache APISIX 可以无缝扩容,所以后续也会去进行 Ingress Controller 单体架构的部署规划。在这种场景下,Apache APISIX 可以脱离 etcd 单独部署,声明式配置可以被 Apache APISIX Ingress Controller 监听并同步到 Apache APISIX。</p> <p>更多未来规划以及特性相关内容<a href="https://github.com/apache/apisix-ingress-controller/milestones">点此查阅</a>。</p> <p>社区的发展是永无止境的,很感谢一路以来各位用户对 Apache APISIX Ingress Controller 的支持。希望大家在后续使用过程中,可以积极地参与和反馈关于 Apache APISIX Ingress Controller 项目的任何问题,让产品变得更优秀。</p>

教程篇:如何在 Apache APISIX Ingress Controller 中使用 Cert Manager 管理证书

<p><a href="https://github.com/apache/apisix-ingress-controller">Apache APISIX Ingress Controller</a> 是一款以 <a href="http://apisix.apache.org/">Apache APISIX</a> 作为数据面的 <a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">Kubernetes Ingress Controller</a> 开源工具,目前已经更新到 <a href="https://github.com/apache/apisix-ingress-controller/blob/master/CHANGELOG.md#130">v1.3</a> 版本,实现了如证书管理、负载均衡、金丝雀发布等功能。</p> <p>长久以来,证书管理都不是一件简单的事情,虽然 Apache APISIX Ingress Controller 支持从 Kubernetes Secrets 资源中提取证书和私钥,并转换为 Apache APISIX 可识别的 SSL 对象,但这只是整个证书管理链中的一部分,证书的颁发、轮转、吊销逻辑依然需要管理员执行,尤其当证书数量比较多时,工作量往往并不小,因而会占用管理员不少的时间。</p> <p><a href="https://cert-manager.io/docs/">Cert Manager</a> 是一款致力于在 Kubernetes 平台上简化证书管理的软件,它支持对接许多不同的证书源,如 <a href="https://letsencrypt.org/">Let’s Encrypt</a> 和 <a href="https://www.vaultproject.io/">HashiCorp Vault</a>。</p> <p>如果你在使用 Apache APISIX Ingress Controller 时,遇到了证书管理的麻烦,那么使用 Cert Manager 将会是一个不错的选择,本文将介绍如何通过 Cert Manager 来创建证书并对接到 Apache APISIX Ingress Controller。</p> <h2>步骤一:环境准备</h2> <p>如果你希望按照本文的指导进行实际的操作,请确保以下环境和工具已准备就绪:</p> <ol> <li>准备一个可用的 Kubernetes 集群,开发环境中,你可以使用 <a href="https://kind.sigs.k8s.io/">Kind</a> 和 <a href="https://kubernetes.io/docs/tutorials/hello-minikube/">Minikube</a></li> <li>安装 <a href="https://kubernetes.io/docs/tutorials/hello-minikube/">kubectl</a></li> <li>安装 <a href="https://helm.sh/">Helm v3</a></li> </ol> <blockquote> <p>请注意,下文所有的操作都将在 ingress-apisix 命名空间中执行,因此需要先创建该命名空间:<code>kubectl create namespace ingress-apisix</code></p> </blockquote> <h2>步骤二:安装 Apache APISIX Ingress Controller</h2> <p>我们可以通过 Helm 来安装 Apache APISIX Ingress Controller,包括数据面的 Apache APISIX 和 etcd 集群。</p> <pre><code class="language-shell">helm repo add apisix https://charts.apiseven.com helm repo update helm install apisix apisix/apisix --set gateway.tls.enabled=true --set ingress-controller.enabled=true --namespace ingress-apisix </code></pre> <p>点击查看<a href="https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix/README.md">详细安装介绍</a>。</p> <h2>步骤三:安装 Cert Manager</h2> <p>通过 Helm 来安装 Cert Manager,点击可查看<a href="https://cert-manager.io/docs/installation/">详细安装介绍</a>。</p> <pre><code class="language-shell">helm install cert-manager jetstack/cert-manager --namespace ingress-apisix --set prometheus.enabled=false --set installCRDs=true </code></pre> <p>安装完毕后请等待一会后查看组件的运行状态,确保所有组件都已正常运行,你可以通过如下命令进行查看。</p> <pre><code class="language-shell">kubectl get all -n ingress-apisix </code></pre> <p>返回结果如下所示,表示所有组件都已正常运行。</p> <pre><code class="language-Apache">NAME READY STATUS RESTARTS AGE pod/apisix-5d99956d88-j68sj 1/1 Running 0 63s pod/apisix-69459554d4-btnwn 0/1 Terminating 0 57m pod/apisix-etcd-0 1/1 Running 0 57m pod/apisix-etcd-1 1/1 Running 0 57m pod/apisix-etcd-2 0/1 Running 0 50s pod/apisix-ingress-controller-7b5c767cc7-j62hb 1/1 Running 0 55m pod/cert-manager-5ffd4f6c89-q9f7m 1/1 Running 0 45m pod/cert-manager-cainjector-748dc889c5-nrvkh 1/1 Running 0 45m pod/cert-manager-startupapicheck-kmgxf 0/1 Completed 0 45m pod/cert-manager-webhook-bc964d98b-mkjj7 1/1 Running 0 45m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/apisix-admin ClusterIP 10.96.16.25 &lt;none&gt; 9180/TCP 57m service/apisix-etcd ClusterIP 10.96.232.251 &lt;none&gt; 2379/TCP,2380/TCP 57m service/apisix-etcd-headless ClusterIP None &lt;none&gt; 2379/TCP,2380/TCP 57m service/apisix-gateway NodePort 10.96.118.75 &lt;none&gt; 80:32039/TCP,443:30107/TCP 57m service/apisix-ingress-controller ClusterIP 10.96.13.76 &lt;none&gt; 80/TCP 57m service/cert-manager-webhook ClusterIP 10.96.182.188 &lt;none&gt; 443/TCP 45m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/apisix 1/1 1 1 57m deployment.apps/apisix-ingress-controller 1/1 1 1 57m deployment.apps/cert-manager 1/1 1 1 45m deployment.apps/cert-manager-cainjector 1/1 1 1 45m deployment.apps/cert-manager-webhook 1/1 1 1 45m NAME DESIRED CURRENT READY AGE replicaset.apps/apisix-5d99956d88 1 1 1 63s replicaset.apps/apisix-69459554d4 0 0 0 57m replicaset.apps/apisix-ingress-controller-74c6b5fbdd 0 0 0 57m replicaset.apps/apisix-ingress-controller-7b5c767cc7 1 1 1 55m replicaset.apps/apisix-ingress-controller-7d58db957c 0 0 0 55m replicaset.apps/cert-manager-5ffd4f6c89 1 1 1 45m replicaset.apps/cert-manager-cainjector-748dc889c5 1 1 1 45m replicaset.apps/cert-manager-webhook-bc964d98b 1 1 1 45m NAME READY AGE statefulset.apps/apisix-etcd 2/3 57m NAME COMPLETIONS DURATION AGE job.batch/cert-manager-startupapicheck 1/1 6m24s 45m </code></pre> <blockquote> <p><a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/">Kubernetes Controller Manager</a> 的机制决定了 Pod 名称会有所不同。</p> </blockquote> <h2>步骤四:申请证书并测试</h2> <p>首先我们需要配置证书颁发对象。</p> <pre><code class="language-yaml"># issuer.yaml apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: issuer namespace: ingress-apisix spec: selfSigned: {} </code></pre> <p>并创建自签名证书颁发者。</p> <pre><code class="language-shell">kubectl apply -f issuer.yaml </code></pre> <blockquote> <p>请注意,自签名颁发对象不推荐使用在生产环境中!更多证书颁发对象的配置请参考<a href="https://cert-manager.io/docs/configuration/">这里</a>。</p> </blockquote> <p>然后为域名 <code>httpbin.org</code> 创建一张证书。</p> <pre><code class="language-yaml"># httpbin-cert.yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: httpbin namespace: ingress-apisix spec: secretName: httpbin duration: 2160h # 90d renewBefore: 360h # 15d subject: organizations: - foo commonName: httpbin.org isCA: false privateKey: algorithm: RSA encoding: PKCS1 size: 2048 usages: - server auth dnsNames: - "httpbin.org" - "*.httpbin.org" issuerRef: name: issuer kind: Issuer group: cert-manager.io </code></pre> <pre><code class="language-shell">kubectl apply -f httpbin-cert.yaml </code></pre> <p>此时需要查看对应 Secrets 是否已经被创建。</p> <pre><code class="language-shell">kubectl get secrets -n ingress-apisix httpbin NAME TYPE DATA AGE httpbin kubernetes.io/tls 3 2m5s </code></pre> <p>通过上述验证,该 Secrets 对象的创建事件已经被 Apache APISIX Ingress Controller 捕获到,我们尝试访问 Apache APISIX Ingress Controller 来验证证书是否生效,首先我们需要创建额外的路由对象。</p> <pre><code class="language-shell"># 创建后端 kubectl run httpbin --image kennethreitz/httpbin --namespace ingress-apisix kubectl expose pod httpbin -n ingress-apisix --port 80 </code></pre> <pre><code class="language-yaml"># 定义 ApisixTls 对象 apiVersion: apisix.apache.org/v1 kind: ApisixTls metadata: name: httpbin namespace: ingress-apisix spec: hosts: - httpbin.org secret: name: httpbin namespace: ingress-apisix --- # 定义访问后端的路由 apiVersion: apisix.apache.org/v2beta1 kind: ApisixRoute metadata: name: httpbin namespace: ingress-apisix spec: http: - name: httpbin match: paths: - /* hosts: - httpbin.org backends: - serviceName: httpbin servicePort: 80 </code></pre> <p>接下来访问服务 <code>apisix-gateway</code>。注意,默认情况下该服务的类型为 <code>NodePort</code>,你可以根据需要修改其类型,比如你的 Kubernetes 集群是云厂商托管的,则可以考虑将其修改为 <code>LoadBalancer</code> 类型,以获取一个外部可达的 IP。</p> <p>这里我们通过端口转发的方式将服务映射到本地。</p> <pre><code class="language-shell">kubectl port-forward -n ingress-apisix svc/apisix-gateway 8443:443 </code></pre> <p>然后开始配置访问。</p> <pre><code class="language-shell">curl https://httpbin.org:8443/json --resolve 'httpbin.org:8443:127.0.0.1' -sk { "slideshow": { "author": "Yours Truly", "date": "date of publication", "slides": [ { "title": "Wake up to WonderWidgets!", "type": "all" }, { "items": [ "Why &lt;em&gt;WonderWidgets&lt;/em&gt; are great", "Who &lt;em&gt;buys&lt;/em&gt; WonderWidgets" ], "title": "Overview", "type": "all" } ], "title": "Sample Slide Show" } } </code></pre> <p>经过上述操作,可以看到访问成功,说明证书已经生效。注意,由于证书是自签名的,这里需要加上 <code>-k</code> 选项来忽略证书的校验。</p> <p>此外,如果你想要轮转证书,删除 <code>httpbin</code> 这一 Secret 对象即可,Cert Manager 会立刻创建一个新的 httpbin Secret 对象,并且包含新的证书。</p> <h2>总结</h2> <p>本文主要讲解了如何利用 Cert Manager 在 Apache APISIX Ingress Controller 中进行证书的创建和管理。想了解更多关于 Apache APISIX Ingress 的介绍与内容,可参考<a href="https://apisix.apache.org/zh/blog/2021/10/09/apisix-ingress-techblog/">本篇文章</a>。</p> <p>或者参与 Apache APISIX Ingress 项目每两周举行的线上讨论,分享当下项目进度、最佳实践及设计思路等多个话题,可查看具体 <a href="https://github.com/apache/apisix-ingress-controller/issues/614">issue</a> 了解更多。</p>

保姆级教程,从概念到实践帮你快速上手 Apache APISIX Ingress

<h2>Apache APISIX Ingress 概览</h2> <h3>Apache APISIX Ingress 定义</h3> <p>在 K8s 生态中,Ingress 作为表示 K8s 流量入口的一种资源,想要让其生效,就需要有一个 Ingress Controller 去监听 K8s 中的 Ingress 资源,并对这些资源进行相应规则的解析和实际承载流量。在当下趋势中,像 Kubernetes Ingress Nginx 就是使用最广泛的 Ingress Controller 实现。</p> <p>而 APISIX Ingress 则是另一种 Ingress Controller 的实现。跟 Kubernetes Ingress Nginx 的区别主要在于 APISIX Ingress 是以 Apache APISIX 作为实际承载业务流量的数据面。如下图所示,当用户请求到具体的某一个服务/API/网页时,通过外部代理将整个业务流量/用户请求传输到 K8s 集群,然后经过 APISIX Ingress 进行后续处理。</p> <p><img src="https://static.apiseven.com/202108/1633765366863-8964a75c-0c16-4683-ad9b-c8c83ac64ec6.png" alt="APISIX Ingress 架构" referrerpolicy="no-referrer"></p> <p>从上图可以看到,APISIX Ingress 分成了两部分。一部分是 APISIX Ingress Controller,作为控制面它将完成配置管理与分发。另一部分 APISIX Proxy Pod 负责承载业务流量,它是通过 CRD(Custom Resource Definitions) 的方式实现的。Apache APISIX Ingress 除了支持自定义资源外,还支持原生的 K8s Ingress 资源。</p> <h3>Apache APISIX 简述</h3> <p>前边我们提到了 APISIX Ingress 是采用 Apache APISIX 作为实际承载业务流量的数据面,那么 Apache APISIX 项目又是做什么的呢?</p> <p><img src="https://static.apiseven.com/202108/1633765402660-6b20dd1c-bef6-4dcb-974e-fa80334e0623.png" alt="Apache APISIX 架构" referrerpolicy="no-referrer"></p> <p>Apache APISIX 是 Apache 基金会旗下的顶级开源项目,也是当前最活跃的开源网关项目。作为一个动态、实时、高性能的开源 API 网关,Apache APISIX 提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。</p> <p>Apache APISIX 可以帮助企业快速、安全地处理 API 和微服务流量,比如限流认证、日志安全功能,以及支持丰富的自定义插件。目前也与很多开源项目如 Apache SkyWalking、Prometheus 等之类的组件进行了相关集成。</p> <h2>APISIX Ingress vs K8s Ingress Nginx</h2> <p>因为本人同时参与到了 APISIX Ingress 与 K8s Ingress Nginx 两个项目的开发和维护,所以很多人也会问我,这两个项目做比较的话,到底该如何选择?或者说为什么有了 K8s Ingress Nginx 还要再做 APISIX Ingress。</p> <h3>配置层面</h3> <p>在 APISIX Ingress 中,我们增加了一些丰富且灵活的配置,比如通过单个配置文件去实现灰度部署。但在 K8s Ingress Nginx 中去实现如上效果的话,最少也需要有两个 Ingress 资源文件才可以完成。</p> <h3>丰富度</h3> <p>在丰富度上,由于 Apache APISIX 本身的自带功能丰富且允许多种插件扩展使用,所以使用 APISIX Ingress 就可以省去自己额外配置功能的繁琐步骤,可以将更多的时间投入到实际开发中。</p> <h3>架构分离</h3> <p>APISIX Ingress 采用了数据面与控制面的分离架构,所以用户可以选择将数据面部署在 K8s 集群内部/外部。但 K8s Ingress Nginx 是将控制面和数据面放在了同一个 Pod 中,如果 Pod 或控制面出现一点闪失,整个 Pod 就会挂掉,进而影响到业务流量。</p> <p>这种架构分离,给用户提供了比较方便的部署选择,同时在业务架构调整场景下,也方便进行相关数据的迁移与使用。</p> <h2>APISIX Ingress 特性详解</h2> <p>由于 Apache APISIX 是一个全动态的高性能网关,所以在 APISIX Ingress 自身就支持了全动态,包括路由、SSL 证书、上游以及插件等等。</p> <p>同时 APISIX Ingress 还具有以下特性:</p> <ul> <li>支持 CRD,更容易理解声明式配置;同时状态检查可保证快速掌握声明配置的同步状态</li> <li>支持高级路由匹配规则以及自定义资源,可与 Apache APISIX 官方 50 多个插件 &amp; 客户自定义插件进行扩展使用</li> <li>支持 K8s 原生 Ingress 配置</li> <li>支持流量切分</li> <li>支持 gRPC plaintext 与 TCP 4 层代理</li> <li>服务自动注册发现,无惧扩缩容</li> <li>更灵活的负载均衡策略,自带健康检查功能</li> </ul> <p>以下我们将从 CRD 与自定义资源层面进行详细的介绍。</p> <h3>CRD 扩展</h3> <p>在前面的介绍中我们提到了 CRD,那么 APISIX Ingress 是如何使用 CRD 扩展的?</p> <p><img src="https://static.apiseven.com/202108/1633765449155-0e25f1d0-e62a-4c4f-ab9a-019f609ed5fb.png" alt="CRD 扩展" referrerpolicy="no-referrer"></p> <p>从用户层面来看,当 Client 发起请求,到达 Apache APISIX 后,会直接把相应的业务流量传输到后端(如 Service Pod),从而完成转发过程。此过程不需要经过 Ingress Controller,这样做可以保证一旦有问题出现,或者是进行变更、扩缩容或者迁移处理等,都不会影响到用户和业务流量。</p> <p>同时在配置端,用户通过 kubectl apply,可将自定义 CRD 配置应用到 K8s 集群。Ingress Controller 会持续 watch 这些资源变更,来将相应配置应用到 Apache APISIX。</p> <h3>自定义资源</h3> <p>APISIX Ingress 目前已经支持的自定义资源主要是以下 5 类,涉及到路由、上游、消费者、证书相关和集群公共配置的相关类别。</p> <h4>APISIX Route(路由)</h4> <p>自定义资源 APISIX Route 中 <code>spec</code> 属性的顶级配置是 <code>http</code>。但其实 <code>spec</code> 是同时支持两种配置的,一种是下图示例的 <code>spec.http</code>,主要用于 7 层代理;另一种是 <code>spec.stream</code>,用于 4 层代理。在配置文件中,我们首先为其自定义了一项规则,即 match 下的相关参数。</p> <p><img src="https://static.apiseven.com/202108/1633765501091-e64ff6e5-5e3e-4b0f-adcc-7ff418edb52c.png" alt="APISIX Route" referrerpolicy="no-referrer"></p> <p>如上图后端配置示例使用了同一个 Service,实际使用中大家根据场景进行调整即可。需要注意的是,<code>weight</code> 属性是用来配置相关 Service 权重。通过以上配置,从而实现一套完整的路由自定义资源。</p> <h4>APISIX Upstream(上游)</h4> <p>在配置 APISIX Upstream 时,需要注意 <code>name</code> 的内容要与 K8s 集群的 Service 保持一致,这样可以保证后续 APISIX Ingress Controller 准确匹配其相应流量。</p> <p><img src="https://static.apiseven.com/202108/1633765534667-3ce978ae-2d85-4de7-8a57-3c5be5f57604.png" alt="APISIX Upstream" referrerpolicy="no-referrer"></p> <p>在配置文件中,<code>spec.loadbalancer</code> 主要负责负载均衡策略的设置,有多种策略模式可供选择。<code>spec.schem</code>e 则是协议类型的配置,目前只支持 HTTP 和 gRPC 协议。<code>spec.healthCheck</code> 主要是对健康检查功能进行设置,比如设置其活跃状态、生效协议与路径和最终反馈等参数配置。</p> <h4>APISIX Consumer(消费者)</h4> <p>在 APISIX Consumer 配置中,主要是增加了认证相关的功能,比如 <code>spec.authParameter</code>,目前该配置参数支持 <code>BasicAuth</code> 与 <code>KeyAuth</code> 这两种比较常见的认证类型。</p> <p><img src="https://static.apiseven.com/202108/1633765580844-9d17d699-fa45-4b43-9ed9-f8ea9c9cab48.png" alt="APISIX Consumer" referrerpolicy="no-referrer"></p> <p>通过 <code>value</code> 可直接去配置相关的 <code>username</code> 和 <code>password</code>,或者直接使用 <code>secret</code> 进行配置,相比前者的明文配置会更安全一些。</p> <h4>APISIX TLS(证书)</h4> <p>APISIX TLS 主要是为了进行证书的管理。如示例所示,用户可以通过 <code>hosts</code> 来配置多个域名,<code>secret</code> 下的参数就是对应的配置证书。</p> <p><img src="https://static.apiseven.com/202108/1633765614989-88b363c2-3805-4159-abfc-bac1b055559b.png" alt="APISIX TLS" referrerpolicy="no-referrer"></p> <p>同时 APISIX TLS 还配有 <code>spec.client</code>,用于进行 mTLS 双向认证的配置。</p> <h4>APISIX Config 相关</h4> <p>关于自定义资源支持的 Config 类型我们会从两个方面进行描述。</p> <p><img src="https://static.apiseven.com/202108/1633765647605-6ad1ba44-06fd-475d-a6ae-925b3cc9c1ce.png" alt="APISIX Cluster Config" referrerpolicy="no-referrer"></p> <p>一种是 APISIX Cluster Config,它主要用于一些通用配置。目前支持在 K8s 或者 Apache APISIX 中全局使用 Prometheus 插件/全局配置 SkyWalking,后续开发中也会去增加一些其他的通用配置。</p> <p>另一种就是我们现在正在 PR 中的 <a href="https://github.com/apache/apisix-ingress-controller/pull/689">APISIX Plugin Config</a>。大家如果感兴趣的话,也可以点击链接来一起参与讨论。Plugin Config 主要是将通用的插件配置统一集合在一起,比如一些同样的配置,用户就可以通过 APISIX Plugin Config 同时应用在多个路由当中,省去了额外多项独立配置的繁琐步骤。</p> <h2>APISIX Ingress 上手实践</h2> <p>目前大家可以通过 <a href="https://github.com/apache/apisix-helm-chart">Helm Charts</a> 的方式来进行 APISIX Ingress 的部署。通过一条命令,就可以同时把 Apache APISIX 以及 APISIX Ingress,包括 Apache APISIX 所需要用到的 etcd 全部部署好,步骤非常简单。</p> <p><img src="https://static.apiseven.com/202108/1633765686788-156b0641-aa78-4de8-833d-a187772470a5.png" alt="安装步骤" referrerpolicy="no-referrer"></p> <h3>实践场景一:流量切分</h3> <p>通过使用 APISIX Ingress 可以实现按比例进行流量切分的效果,具体操作如下:</p> <h4>步骤一:配置 APISIX Upstream</h4> <p><img src="https://static.apiseven.com/2022/09/19/632828d22987f.png" alt="配置 APISIX Upstream" referrerpolicy="no-referrer"></p> <h4>步骤二:配置 APISIX Route</h4> <p>通过在 <code>backends</code> 中去配置 <code>subset</code> 和 <code>weight</code>,来实现用户请求流量进入时的分流。如下图示例就是 90% 的流量会进入到 v1 中,10% 的流量进入到 v2 中。</p> <p><img src="https://static.apiseven.com/202108/1633765771090-1e51e66c-0979-43b4-852b-28f2284a5d4e.png" alt="配置 APISIX Route" referrerpolicy="no-referrer"></p> <p>通过以上两步,就可以十分方便地按比例进行流量切分,实现类似灰度发布等场景需求。 更多具体操作细节也可参考:<a href="https://www.apiseven.com/blog/traffic-split-in-apache-apisix-ingress-controller">Apache APISIX Ingress Controller 中的流量切分</a>。</p> <h3>实践场景二:配置认证</h3> <p>如果想在 APISIX Ingress 中为某些路由配置 Basic Auth,可以参考如下操作:</p> <h4>步骤一:创建 APISIX Consumer 资源</h4> <p>如前文所提到的,可以在 APISIX Consumer 配置中增加 <code>basicAuth</code>,并为其指定用户名和密码。</p> <p><img src="https://static.apiseven.com/202108/1633765803898-7a30c663-7ba8-4064-8772-a19c56cef191.png" alt="创建资源" referrerpolicy="no-referrer"></p> <h4>步骤二:配置 APISIX Route,增加认证相关参数</h4> <p>在自定义资源 APISIX Route 中,通过在后端添加 <code>authenticatio</code>n,将其开启并指定认证类型即可。</p> <p><img src="https://static.apiseven.com/202108/1633765828596-9a0f0142-f201-4004-b85d-a34de4ee13dc.png" alt="增加认证参数" referrerpolicy="no-referrer"></p> <p>通过以上步骤,就可以实现使用 Consumer 去完成相关配置认证。</p> <h3>实践场景三:K8s 资源扩展</h3> <p>正如我们在开头提到过的,APISIX Ingress 不仅支持自定义资源,还同时支持 K8s 原生的 Ingress 资源。</p> <p><img src="https://static.apiseven.com/202108/1633765859904-bc48dcc5-cd7a-4875-b248-5c4c64a2d7c5.png" alt="K8s 原生资源" referrerpolicy="no-referrer"></p> <p>如上图是 K8s Ingress 资源。通常情况下如果想要在资源上做 rewrite,可以通过增加 annotation 配置属性。这样当用户携带 <code>httpbin.org</code> 请求时,就可以通过路径 /sample 将它重定向到 /ip。</p> <p>当上述需求使用 APISIX Ingress 时,只需在 Ingress 增加一个 <code>kubernetes.io/ingress.class: apisix</code>,去指定 APISIX Ingress Controller 去监听这个资源,同时通过配置 <code>k8s.apisix.apache.org/rewrite-target: "/ip"</code>,就可以完成重定向到 /ip 路径。</p> <p><img src="https://static.apiseven.com/202108/1633765888876-d2d252ee-706c-49f3-b630-03a7e72a0620.png" alt="APISIX Ingress 资源" referrerpolicy="no-referrer"></p> <p>以上示例只是目前 APISIX Ingress 对于原生 K8s Ingress 支持的一种方式,更多示例大家可以查看<a href="https://apisix.apache.org/docs/ingress-controller/practices/proxy-the-httpbin-service-with-ingress">具体文档</a>进行参考使用。</p> <h2>未来规划</h2> <p>之后 APISIX Ingress 将会继续在功能与生态上进行更新,目前阶段已经完成了 <a href="https://apisix.apache.org/docs/ingress-controller/tutorials/manage-certificates-with-cert-manager/">APISIX Ingress 与 Cert-manager 集成</a>,后续将逐步实现以下目标:</p> <ol> <li>完成 Kubernetes V1.22+ 与 CRD V1 版本的适配支持(已经完成,即将在 APISIX Ingress V1.3 版本 中发布)</li> <li>支持 Gateway API(预计在 Q4 阶段实现)</li> <li>扩展新架构,以便于用户在不需要使用 etcd 的情况下,可以正常使用 APISIX Ingress</li> <li>丰富产品生态,扩展 APISIX Ingress 社区</li> </ol> <p>最后也希望大家能够多多地参与到项目中来,比如每两周的周三下午 2 点都会有一次 APISIX Ingress 社区会议,会跟大家同步一下当前的项目进展或者遇到的问题。大家可以持续关注 Apache APISIX 视频号,届时可以直接参与社区会议直播。</p> <p><a href="https://github.com/apache/apisix-ingress-controller/issues/614">点此查看</a>更多关于 APISIX Ingress 社区会议细节。</p>

使用 Apache APISIX 进行集中式身份认证及进阶玩法

<p>身份认证在日常生活当中是非常常见的一项功能,大家平时基本都会接触到。比如用支付宝消费时的人脸识别确认、公司上班下班时的指纹/面部打卡以及网站上进行账号密码登录操作等,其实都是身份认证的场景体现。</p> <p><img src="https://static.apiseven.com/202108/1631004418593-0a46f949-72aa-4cd4-8f38-1988327c92d6.png" alt="概念理解" referrerpolicy="no-referrer"></p> <p>如上图,Jack 通过账号密码请求服务端应用,服务端应用中需要有一个专门用做身份认证的模块来处理这部分的逻辑。请求处理完毕子后,如果使用 JWT Token 认证方式,服务器会反馈一个 Token 去标识这个用户为 Jack。如果登录过程中账号密码输入错误,就会导致身份认证失败。这个场景大家肯定都非常熟悉,这里就不做展开举例了。</p> <h2>网络身份认证的意义在哪里?</h2> <h3>安全性</h3> <p>身份认证确保了后端服务的安全性,避免了未经授权的访问,从而确保数据的安全性。比如防止某些黑客攻击,以及一些恶意调用,这些都可以通过身份认证进行阻拦。</p> <h3>实用性</h3> <p>从实用性的角度考虑,它可以更方便地记录操作者或调用方。比如上班打卡其实就是通过记录「谁进行了这项操作」来统计员工上班信息。</p> <p>其次它可以记录访问频率及访问频次。例如记录某用户在最近几分钟中发起请求的频率和频次,可以判断这个用户活跃程度,也可以判断是否为恶意攻击等。</p> <h3>功能性</h3> <p>在功能层面,它通过识别身份可以对不同的身份进行不同权限的操作处理。比如在公司里,你的身份权限无法使用某些页面或服务。再细致一点,对不同身份或者不同的 API 接口调用方做一些相关技术上的限制策略,如限流限速等,以此来达到根据不同的用户(调用方)采取不同的功能限制。</p> <h2>使用 Apache APISIX 进行集中式身份认证优点</h2> <h3>从传统到新模式</h3> <p>如下图所示,左侧的图为我们展示了一种比较常见的传统身份认证方法。每一个应用服务模块去开发一个单独的身份认证模块,用来支持身份认证的一套流程处理。但当服务量多了之后,就会发现这些模块的开发工作量都是非常巨大且重复的。</p> <p><img src="https://static.apiseven.com/202108/1631004492221-0721d933-705d-4875-b956-e94a11a45135.png" alt="Apache APISIX 身份认证" referrerpolicy="no-referrer"></p> <p>这种时候,我们可以通过把这部分的开发逻辑放置到 Apache APISIX 的网关来实现统一,减少开发量。</p> <p>图右所示,用户或应用方直接去请求 Apache APISIX,然后 Apache APISIX 通过识别并认证通过后,会将鉴别的身份信息传递到上游应用服务。之后上游应用服务就可以从请求头中读到这部分信息,然后进行后续相关工作的处理。</p> <p>讲完了大概流程,我们来详细罗列下。</p> <h3>优点一:配置收敛,统一管理</h3> <p><img src="https://static.apiseven.com/202108/1631004574541-87b607eb-2971-4c1d-a1d6-74cf4a5fdd42.png" alt="Dashboard" referrerpolicy="no-referrer"></p> <p>如上图是一张 APISIX-Dashboard 的界面截图,可以看到路由、Consumer 等数据信息。这里的 Consumer 可以理解为一个应用方,比如可以为应用专门去创建一个 Consumer 并配置相关的认证插件,例如 JWT、Basic-Auth 等插件。当有新的服务出现时,我们需要再创建一个 Consumer,然后将这部分配置信息给配置到第二个应用服务上。</p> <p>通过集中式身份认证,可以将客户配置进行收敛并统一管理,达到降低运维成本的效果。</p> <h3>优点二:插件丰富,减少开发</h3> <p>Apache APISIX 作为一个 API 网关,目前已开启与各种插件功能的适配合作,插件库也比较丰富。目前已经可与大量身份认证相关的插件进行搭配处理,如下图所示。</p> <p><img src="https://static.apiseven.com/202108/1631004738218-586e84af-a5ab-4714-845d-4d71b7ba79e3.png" alt="API 网关认证插件" referrerpolicy="no-referrer"></p> <p>基础认证插件比如 Key-Auth、Basic-Auth,他们是通过账号密码的方式进行认证。</p> <p>复杂一些的认证插件如 Hmac-Auth、JWT-Auth。如 Hmac-Auth 通过对请求信息做一些加密,生成一个签名。当 API 调用方将这个签名携带到 Apache APISIX 的网关 Apache APISIX 会以相同的算法计算签名,只有当签名方和应用调用方认证相同时才予以通过。</p> <p>Authz-casbin 插件是目前 Apche APISIX 与 Casbin 社区正在进行合作开发的一个项目,它的应用原理是按照 Casbin 的规则,去处理一些基于角色的权限管控 (RBAC),进行 ACL 相关操作。</p> <p>其他则是一些通用认证协议和联合第三方组件进行合作的认证协议,例如 OpenID-Connect 身份认证机制,以及 LDAP 认证等。</p> <h3>优点三:配置灵活,功能强大</h3> <p>功能强大该如何理解,就是 Apache APISIX 中可以针对每一个 Consumer (即调用方应用)去做不同级别的插件配置。</p> <p><img src="https://static.apiseven.com/202108/1631004783828-3dd0056c-a6aa-4ab9-b902-7bd2ca545ffe.png" alt="配置灵活" referrerpolicy="no-referrer"></p> <p>如上图,我们创建了两个消费者 Consumer A,Consumer B。我们将 Consumer A 应用到<code>应用1</code>,则后续<code>应用1</code>的访问将会开启 Consumer A 的这部分插件,例如 IP 黑白名单,限制并发数量等。将 Consumer B 应用到<code>应用2</code> ,由于开启了 http-log 插件,则<code>应用2</code>的访问日志将会通过 HTTP 的方式发送到日志系统进行收集。</p> <h2>Apache APISIX 中身份认证的玩法</h2> <p>关于 Apache APISIX 身份认证的玩法,这里为大家提供三个阶段的玩法推荐,大家仅作参考,也可以在这些基础上,进行更深度的使用和应用。</p> <h3>初级:普通玩法</h3> <p>普通玩法推荐大家基于 Key-Auth、Basic-Auth、JWT- Auth 和 Hmac-Auth 进行,这些插件的使用需要与 Consumer 进行关联使用。</p> <h4>步骤一:创建路由,配置身份认证插件</h4> <p>创建路由,配置上游为 <code>httpbin.org</code>,同时开启 <code>basic-auth</code> 插件。</p> <p><img src="https://static.apiseven.com/202108/1631004892467-71c93f8f-dc0e-47fe-a88f-943adb9edbff.png" alt="创建路由" referrerpolicy="no-referrer"></p> <h4>步骤二:创建消费者,配置身份信息</h4> <p>创建消费者 foo。在消费者中,我们需要配置用户的认证信息,例如 <code>username</code> 为 foo,<code>password</code> 为 <code>bar</code>。</p> <p><img src="https://static.apiseven.com/202108/1631004937828-15ac5d8f-0e45-4c3d-94e8-2b180266b379.png" alt="创建消费者" referrerpolicy="no-referrer"></p> <h4>步骤三:应用服务携带认证信息进行访问</h4> <p>应用携带 <code>username:foo</code> ,<code>password: bar</code> 访问 Apache APISIX。Apache APISIX 通过认证,并将请求 Authorization 请求头携带至上游 <code>httpbin.org</code> 。由于 <code>httpbin.org</code> 中 get 接口会将请求信息返回,因此我们可以在其中观察到请求头 <code>Authorization</code>。</p> <p><img src="https://static.apiseven.com/202108/1631004973305-4b209f79-f7de-41a2-994e-8877a6624d99.png" alt="利用 APISIX 进行认证访问" referrerpolicy="no-referrer"></p> <h3>中级:进阶玩法</h3> <p>进阶模式下,是使用 Apache APISIX 与 OpenID-Connect 插件进行对接第三方认证服务。OpenID-Connect 是一种认证机制,可以采用该认证机制对接用户的用户管理系统或者用户管理服务,例如国内的 Authing 和腾讯云,国外的 Okta 和 Auth0 等。</p> <p><img src="https://static.apiseven.com/202108/1631005002268-7393b40e-1733-4e66-bc09-742be221efae.png" alt="第三方认证模式" referrerpolicy="no-referrer"></p> <p>具体操作如下,这里使用 Okta 云服务举例:</p> <h4>步骤一:创建 OpenID-Connect 应用</h4> <p>在 Okta 控制台页面创建一个支持 OpenID-Connect 的应用。</p> <p><img src="https://static.apiseven.com/202108/1631005022640-1e931b14-8175-47f3-bfb8-46e09cec616b.png" alt="创建" referrerpolicy="no-referrer"></p> <h4>步骤二:创建路由,配置 OpenID-Connect 插件</h4> <p>创建路由,配置访问的上游地址为 httpbin.org,同时开启 openid-connect 插件,将 Okta 应用的配置填写到 openid-connect 插件中。</p> <p><img src="https://static.apiseven.com/202108/1631005045489-b637ef9a-c71c-440f-ae58-a93398a4c9dd.png" alt="配置插件" referrerpolicy="no-referrer"></p> <h4>步骤三:用户访问时,会跳转至登录页面。登录完成后,上游应用获取用户信息</h4> <p>此时,当用户访问 Apache APISIX 时,会先重定向到 Okta 登录页面。此时需要在该页面填写 Okta 中已经存在的用户的账号密码。登陆完成之后,Apache APISIX 会将本次认证的 Access-Token,ID-Token 携带至上游,同时将本次认证的用户信息以 base64 的方式编码至 Userinfo 请求头,传递至上游。</p> <p><img src="https://static.apiseven.com/202108/1631005077846-0f877a03-ddcd-46f6-a38d-f046b4700058.png" alt="APISIX 页面" referrerpolicy="no-referrer"></p> <h2>高级:DIY 玩法</h2> <p>这里提供的 DIY 玩法是利用了 Serverless 插件,这款插件功能丰富,玩法也非常多。大家如果有自己的使用玩法,也欢迎在社区内进行交流。</p> <p>具体操作流程就是将自己的代码片段通过 Serverless 插件上传到 Apache APISIX,这个过程中 Serverless 插件支持动态配置和热更新。</p> <h3>方式一:自定义判断逻辑</h3> <p><img src="https://static.apiseven.com/202108/1631005112469-c04868b8-388e-4b81-abcc-d37b6a8951f5.png" alt="判断逻辑" referrerpolicy="no-referrer"></p> <p>通过请求头、参数等相关信息,来进行一些判断。通过这种做法,我们可以去实现自己的一些规则,比如说结合企业内部的一些认证,或者去写一些自己的业务逻辑。</p> <h3>方式二:发起认证鉴权请求</h3> <p><img src="https://static.apiseven.com/202108/1631005141578-f90cf948-4913-45cd-a28e-9e697ad197fe.png" alt="鉴权请求" referrerpolicy="no-referrer"></p> <p>通过 HTTP 库发起一个 HTTP 请求,我们可以利用第三方服务做一些认证、鉴权相关事情,然后解析返回结果。通过这种方式,我们可以做的事情就可以扩展很多。比如对接 Redis 或数据库,只要是通过TCP 或 UDP 这种形式的,都可以通过 Serverless 插件进行尝试。</p> <h3>配置上传</h3> <p>完成自定义代码片之后,我们创建路由,并将代码片配置到 Serverless 插件。后面再通过访问 Apache APISIX 获取相应的信息反馈,测试插件是否生效。</p> <p><img src="https://static.apiseven.com/202108/1631005184917-bc620c0b-d4c6-43f5-8450-4f5b2b9549e1.png" alt="配置上传" referrerpolicy="no-referrer"></p>

Python 助你快速上手 Apache APISIX 插件开发

<p>在 <code>Apache APISIX Python Runner</code> 之前社区中已经支持了 <a href="https://github.com/apache/apisix-java-plugin-runner">Java</a> 和 <a href="https://github.com/apache/apisix-go-plugin-runner">Go</a> 语言的 <code>Runner</code> ,今天 <code>Python Runner</code> 也来了,社区中的小伙伴们在开发 <code>Apache APISIX</code> 的插件时又多了一种新选择。</p> <h2>简介</h2> <h3>Apache APISIX</h3> <p><code>Apache APISIX</code> 是一个高性能的云原生开源 API 网关,它可以对请求进行统一的拦截和治理(如:鉴权、认证、缓存、版本、熔断、审计 等等)帮助开发人员轻松的对外提供安全可靠的服务,而开发人员通过 <code>Apache APISIX</code> 的加持只需要关注业务实现即可,省去了大量花费在通用能力上的开发与维护上的时间并且也降低了整体业务架构的复杂度。</p> <h3>Python</h3> <p><code>Python</code> 语言作为一个解释型的高级编程语言,它 <code>语法简洁易上手</code>、<code>代码可读性好</code> ,在 <code>跨平台</code> 、<code>可移植性</code> 、<code>开发效率</code> 上都有很好的表现,同时作为一个高级编程语言它的封装抽象程度比较高屏蔽了很多底层细节(例如:<code>GC</code> )让我们在开发的过程中可以更专注应用逻辑的开发。<code>Python</code> 作为一个有 30 年历史的老牌开发语言,它的生态以及各种模块已经非常完善,我们大部分的开发和应用场景都可以从社区中找到很成熟的模块或解决方案。<code>Python</code> 其他的优点就不再一一赘述。<code>Python</code> 的缺点也比较明显:<code>Python</code> 作为一门解释性语言,相较于 <code>C++</code> 和 <code>Go</code> 这样的编译型语言,在性能上的差距还是比较大的。</p> <h3>Apache APISIX Python Runner</h3> <p><a href="https://github.com/apache/apisix-python-plugin-runner">apache-apisix-python-runner</a> 这个项目可以理解为 <code>Apache APISIX</code> 和 <code>Python</code> 之间的一道桥梁,通过 <code>Python Runner</code> 可以把 <code>Python</code> 直接应用到 <code>APISIX</code> 的插件开发中,最重要的还是希望让更多对 <code>Apache APISIX</code> 和 <code>API 网关</code> 感兴趣的 <code>Python开发者</code> 通过这个项目更多的了解使用 <code>Apache APISIX</code>,以下为 <code>Apache APISIX</code> 多语言支持的架构图。</p> <p><img src="https://static.apiseven.com/202108/1631168726051-76d50225-dd9f-4ec4-9995-4da6c6510ce7.png" alt="Apache APISIX work flow" referrerpolicy="no-referrer"></p> <p>上图左边是 <code>Apache APISIX</code> 的工作流程,右边的 <code>Plugin Runner</code> 是各语言的插件运行器,本文介绍的 <code>apisix-python-plugin-runner</code> 就是支持 <code>Python</code> 语言的 <code>Plugin Runner</code>。</p> <p>当你在 <code>Apache APISIX</code> 中配置一个 <code>Plugin Runner</code> 时,<code>Apache APISIX</code> 会启动一个子进程运行 <code>Plugin Runner</code>,该子进程与 <code>Apache APISIX</code> 进程属于同一个用户,当我们重启或重新加载 <code>Apache APISIX</code> 时,<code>Plugin Runner</code> 也将被重启。</p> <p>如果你为一个给定的路由配置了 <code>ext-plugin-*</code> 插件,请求命中该路由时将触发 <code>Apache APISIX</code> 通过 <code>Unix Socket</code> 向 <code>Plugin Runner</code> 发起 <code>RPC</code> 调用。调用分为两个阶段:</p> <ul> <li><a href="https://github.com/apache/apisix/blob/master/docs/en/latest/plugins/ext-plugin-pre-req.md">ext-plugin-pre-req</a> :在执行 <code>Apache APISIX</code> 内置插件(Lua 语言插件)之前</li> <li><a href="https://github.com/apache/apisix/blob/master/docs/en/latest/plugins/ext-plugin-post-req.md">ext-plugin-post-req</a> :在执行 <code>Apache APISIX</code> 内置插件(Lua 语言插件)之后</li> </ul> <p>大家可以根据需要选择并配置 <code>Plugin Runner</code> 的执行时机。</p> <p><code>Plugin Runner</code> 会处理 <code>RPC</code> 调用,在其内部创建一个模拟请求,然后运行多语言编写的插件,并将结果返回给 Apache APISIX。</p> <p>多语言插件的执行顺序是在 <code>ext-plugin-*</code> 插件配置项中定义的,像其他插件一样,它们可以被启用并在运行中重新定义。</p> <h2>部署测试</h2> <h3>基础运行环境</h3> <ul> <li>Apache APISIX 2.7</li> <li>Python 3.6+</li> </ul> <p><code>Apache APISIX</code> 的安装部署本文不在过多赘述,详情请参考 <a href="https://apisix.apache.org/zh/docs/apisix/how-to-build/">Apache APISIX 官方文档:如何构建 Apache APISIX</a> 进行部署。</p> <h3>下载安装 Python Runner</h3> <pre><code class="language-bash">$ git clone https://github.com/apache/apisix-python-plugin-runner.git $ cd apisix-python-plugin-runner $ make install </code></pre> <h3>配置 Python Runner</h3> <h4>开发模式</h4> <h5>运行 Python Runner</h5> <pre><code class="language-bash">$ cd /path/to/apisix-python-plugin-runner $ APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock python3 apisix/main.py start </code></pre> <h5>修改 APISIX 配置文件</h5> <pre><code class="language-bash">$ vim /path/to/apisix/conf/config.yaml apisix: admin_key: - name: "admin" key: edd1c9f034335f136f87ad84b625c8f1 role: admin ext-plugin: path_for_test: /tmp/runner.sock </code></pre> <h4>生产模式</h4> <h5>修改 APISIX 配置文件</h5> <pre><code class="language-bash">$ vim /path/to/apisix/conf/config.yaml apisix: admin_key: - name: "admin" key: edd1c9f034335f136f87ad84b625c8f1 role: admin ext-plugin: cmd: [ "python3", "/path/to/apisix-python-plugin-runner/apisix/main.py", "start" ] </code></pre> <h4>Python Runner 配置(可选)</h4> <p>如果需要对 <code>Log Level</code> 或 <code>Unix Domain Socket</code> 环境变量调整可以修改 <code>Runner</code> 的配置文件</p> <pre><code class="language-bash">$ vim /path/to/apisix-python-plugin-runner/apisix/config.yaml socket: file: $env.APISIX_LISTEN_ADDRESS # Environment variable or absolute path logging: level: debug # error warn info debug </code></pre> <h3>启动 Python Runner</h3> <pre><code class="language-bash">$ cd /path/to/apisix # Start or Restart $ ./bin/apisix [ start | restart ] </code></pre> <p>启动或重启 <code>APISIX</code> 即可,此时 <code>APISIX</code> 和 <code>Python Runner</code> 已经完成配置并启动。</p> <h3>测试 Python Runner</h3> <h4>配置 Apache APISIX 路由及插件信息</h4> <pre><code class="language-bash"># 使用默认demo插件进行测试 $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/get", "plugins": { "ext-plugin-pre-req": { "conf": [ { "name": "stop", "value":"{\"body\":\"hello\"}"} ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }' </code></pre> <ul> <li><code>plugins.ext-plugin-pre-req.conf</code> 为 <code>Runner</code> 插件配置,<code>conf</code> 为数组格式可以同时设置多个插件。</li> <li>插件配置对象中 <code>name</code> 为插件名称,该名称需要与插件代码文件和对象名称一直。</li> <li>插件配置对象中 <code>value</code> 为插件配置,可以为 <code>JSON</code> 字符串。</li> </ul> <h4>访问验证</h4> <pre><code class="language-bash">$ curl http://127.0.0.1:9080/get -i HTTP/1.1 200 OK Date: Fri, 13 Aug 2021 13:39:18 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive host: 127.0.0.1:9080 accept: */* user-agent: curl/7.64.1 X-Resp-A6-Runner: Python Server: APISIX/2.7 Hello, Python Runner of APISIX </code></pre> <h2>插件开发</h2> <h3>插件目录</h3> <pre><code class="language-bash">/path/to/apisix-python-plugin-runner/apisix/plugins </code></pre> <p>此目录中的 <code>.py</code> 文件将会被自动加载。</p> <h3>插件示例</h3> <pre><code class="language-bash">/path/to/apisix-python-plugin-runner/apisix/plugins/stop.py /path/to/apisix-python-plugin-runner/apisix/plugins/rewrite.py </code></pre> <h3>插件格式</h3> <pre><code class="language-python">from apisix.runner.plugin.base import Base from apisix.runner.http.request import Request from apisix.runner.http.response import Response class Stop(Base): def __init__(self): """ Example of `stop` type plugin, features: This type of plugin can customize response `body`, `header`, `http_code` This type of plugin will interrupt the request """ super(Stop, self).__init__(self.__class__.__name__) def filter(self, request: Request, response: Response): """ The plugin executes the main function :param request: request parameters and information :param response: response parameters and information :return: """ # 在插件中可以通过 `self.config` 获取配置信息,如果插件配置为JSON将自动转换为字典结构 # print(self.config) # 设置响应头信息 headers = request.headers headers["X-Resp-A6-Runner"] = "Python" response.headers = headers # 设置响应体信息 response.body = "Hello, Python Runner of APISIX" # 设置响应状态码 response.status_code = 201 # 通过调用 `self.stop()` 中断请求流程,此时将立即响应请求给客户端 # 如果未显示调用 `self.stop()` 或 显示调用 `self.rewrite()`将继续将请求 # 默认为 `self.rewrite()` self.stop() </code></pre> <h3>插件规范及注意事项</h3> <ul> <li>实现插件对象必须继承 <code>Base</code> 类</li> <li>插件必须实现 <code>filter</code> 函数</li> <li><code>filter</code> 函数参数只能包含 <code>Request</code> 和 <code>Response</code> 类对象作为参数</li> <li><code>Request</code> 对象参数可以获取请求信息</li> <li><code>Response</code> 对象参数可以设置响应信息</li> <li><code>self.config</code> 可以获取插件配置信息</li> <li><code>filter</code> 函数中调用 <code>self.stop()</code> 时将马上中断请求,响应数据。</li> <li><code>filter</code> 函数中调用 <code>self.rewrite()</code> 时,将会继续请求。</li> </ul> <h2>欢迎参与</h2> <p>目前 <code>Apache APISIX</code> 各语言的 <code>Runner</code> 还处于早期开发阶段,我们会陆续完善其功能。成功的开源项目离不开大家的参与和贡献,欢迎各位参与 <code>Apache APISIX Runner</code> 的开发,让我们一起共建 <code>Apache APISIX</code> 与各语言的桥梁。</p> <ul> <li><a href="https://github.com/apache/apisix-python-plugin-runner">apisix-python-plugin-runner</a></li> <li><a href="https://github.com/apache/apisix-go-plugin-runner">apisix-go-plugin-runner</a></li> <li><a href="https://github.com/apache/apisix-java-plugin-runner">apisix-java-plugin-runner</a></li> </ul> <h2>相关阅读</h2> <ul> <li><a href="http://apisix.apache.org/blog/2021/08/19/go-makes-apache-apisix-better">Go 让 Apache APISIX 如虎添翼</a></li> <li><a href="https://apisix.apache.org/blog/2021/06/21/use-java-to-write-apache-apisix-plugins">如何用 Java 编写 Apache APISIX 插件</a></li> </ul>

Apache APISIX × KubeSphere:提供更好用的网关及 K8S Ingress Controller

<h2>KubeSphere 介绍</h2> <p><a href="https://kubesphere.io/">KubeSphere</a> 是在 Kubernetes 之上构建的面向云原生应用的系统,完全开源,支持多云与多集群管理,提供全栈的 IT 自动化运维能力,简化企业的 DevOps 工作流。它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用 (plug-and-play) 的集成。</p> <p>作为全栈的多租户容器平台,KubeSphere 提供了运维友好的向导式操作界面,帮助企业快速构建一个强大和功能丰富的容器云平台。KubeSphere 为用户提供构建企业级 Kubernetes 环境所需的多项功能,例如多云与多集群管理、Kubernetes 资源管理、DevOps、应用生命周期管理、微服务治理(服务网格)、日志查询与收集、服务与网络、多租户管理、监控告警、事件与审计查询、存储管理、访问权限控制、GPU 支持、网络策略、镜像仓库管理以及安全管理等。</p> <h2>Apache APISIX 介绍</h2> <p><a href="https://github.com/apache/apisix">Apache APISIX</a> 是一款开源的高性能、动态云原生网关,由深圳支流科技有限公司于 2019 年捐赠给 Apache 基金会,当前已经成为 Apache 基金会的顶级开源项目,也是 GitHub 上最活跃的网关项目。Apache APISIX 当前已经覆盖了 API 网关,LB,Kubernetes Ingress,Service Mesh 等多种场景。</p> <h2>前置条件</h2> <p>将现有 Kubernetes 集群已纳入 KubeSphere 管理。</p> <h2>部署 Apache APISIX 和 Apache APISIX Ingress Controller</h2> <p>我们可以参考 KubeSphere 的文档启用 KubeSphere 的 <a href="https://kubesphere.io/docs/pluggable-components/app-store/">AppStore</a>,或者使用使用 Apache APISIX 的 <a href="https://charts.apiseven.com/">Helm 仓库</a>来进行部署。这里,我们直接使用 Apache APISIX 的 Helm 仓库进行部署。</p> <p>执行以下命令即可添加 Apache APISIX 的 Helm repo,并完成部署。</p> <pre><code class="language-shell">➜ ~ helm repo add apisix https://charts.apiseven.com "apisix" has been added to your repositories ➜ ~ helm repo add bitnami https://charts.bitnami.com/bitnami "bitnami" has been added to your repositories ➜ ~ helm repo update ➜ ~ kubectl create ns apisix namespace/apisix created ➜ ~ helm install apisix apisix/apisix --set gateway.type=NodePort --set ingress-controller.enabled=true --namespace apisix W0827 18:19:58.504653 294386 warnings.go:70] apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition NAME: apisix LAST DEPLOYED: Fri Aug 27 18:20:00 2021 NAMESPACE: apisix STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Get the application URL by running these commands: export NODE_PORT=$(kubectl get --namespace apisix -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway) export NODE_IP=$(kubectl get nodes --namespace apisix -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT </code></pre> <p>验证是否已经成功部署且运行:</p> <pre><code class="language-shell">➜ ~ kubectl -n apisix get pods NAME READY STATUS RESTARTS AGE apisix-77d7545d4d-cvdhs 1/1 Running 0 4m7s apisix-etcd-0 1/1 Running 0 4m7s apisix-etcd-1 1/1 Running 0 4m7s apisix-etcd-2 1/1 Running 0 4m7s apisix-ingress-controller-74c6b5fbdd-94ngk 1/1 Running 0 4m7s </code></pre> <p>可以看到相关的 Pod 均已正常运行。</p> <h2>部署示例项目</h2> <p>我们使用 <code>kennethreitz/httpbin</code> 作为示例项目进行演示。这里也直接在 KubeSphere 中完成部署。</p> <p>选择服务 -- 无状态服务,创建即可。</p> <p><img src="https://static.apiseven.com/202108/1630404138226-5475c163-d372-414e-af74-5c5a86f19629.png" alt="KubeSphere APISIX Ingress Controller demo" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1630404173444-9bb73e0d-5bee-428e-a257-4685500344ef.png" alt="KubeSphere APISIX Ingress Controller demo" referrerpolicy="no-referrer"></p> <p>在 KubeSphere 的服务和负载界面即可看到部署成功,也可以直接在终端下查看是否已经部署成功。</p> <pre><code class="language-shell">➜ ~ kubectl get pods,svc -l app=httpbin NAME READY STATUS RESTARTS AGE pod/httpbin-v1-7d6dc7d5f-5lcmg 1/1 Running 0 48s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/httpbin ClusterIP 10.96.0.5 &lt;none&gt; 80/TCP 48s </code></pre> <h2>使用 Apache APISIX 作为网关代理</h2> <p>我们先演示如何使用 Apache APISIX 作为网关代理 Kubernetes 集群中的服务。</p> <pre><code class="language-shell">root@apisix:~$ kubectl -n apisix exec -it `kubectl -n apisix get pods -l app.kubernetes.io/name=apisix -o name` -- bash bash-5.1# curl httpbin.default/get { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.default", "User-Agent": "curl/7.77.0" }, "origin": "10.244.2.9", "url": "http://httpbin.default/get" } </code></pre> <p>可以看到在 Apache APISIX 的 Pod 内可正常访问示例项目。接下来使用 Apache APISIX 对该示例项目进行代理。</p> <p>这里我们使用 <code>curl</code> 调用 Apache APISIX 的 admin 接口,创建一条路由。将所有 host 头为 <code>httpbin.org</code> 的请求转发给 <code>httpbin.default:80</code> 这个实际的应用服务上。</p> <pre><code class="language-shell">bash-5.1# curl "http://127.0.0.1:9180/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "uri": "/get", "host": "httpbin.org", "upstream": { "type": "roundrobin", "nodes": { "httpbin.default:80": 1 } } }' {"node":{"key":"\/apisix\/routes\/1","value":{"host":"httpbin.org","update_time":1630060883,"uri":"\/*","create_time":1630060883,"priority":0,"upstream":{"type":"roundrobin","pass_host":"pass","nodes":{"httpbin.default:80":1},"hash_on":"vars","scheme":"http"},"id":"1","status":1}},"action":"set"} </code></pre> <p>你会得到类似上面的输出,接下来验证是否代理成功:</p> <pre><code class="language-shell">bash-5.1# curl http://127.0.0.1:9080/get -H "HOST: httpbin.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.77.0", "X-Forwarded-Host": "httpbin.org" }, "origin": "127.0.0.1", "url": "http://httpbin.org/get" } </code></pre> <p>得到上面的输出,说明已经通过 Apache APISIX 代理了示例项目的流量。接下来我们试试在集群外通过 Apache APISIX 访问示例项目。</p> <pre><code class="language-shell">root@apisix:~$ kubectl -n apisix get svc -l app.kubernetes.io/name=apisix NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE apisix-admin ClusterIP 10.96.33.97 &lt;none&gt; 9180/TCP 22m apisix-gateway NodePort 10.96.126.83 &lt;none&gt; 80:31441/TCP 22m </code></pre> <p>在使用 Helm chart 部署的时候,默认会将 Apache APISIX 的端口通过 NodePort 的形式暴露出去。我们使用 Node IP + NodePort 的端口进行访问测试。</p> <pre><code class="language-shell">root@apisix:~$ curl http://172.18.0.5:31441/get -H "HOST: httpbin.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.76.1", "X-Forwarded-Host": "httpbin.org" }, "origin": "10.244.2.1", "url": "http://httpbin.org/get" } </code></pre> <p>可以看到,<strong>在集群外已经可以通过 Apache APISIX 作为网关代理 Kubernetes 集群内的服务了。</strong></p> <h2>使用 APISIX Ingress Controller 代理服务</h2> <p>我们可以直接在 KubeSphere 中添加应用路由(Ingress) ,Apache APISIX Ingress Controller 会自动将路由规则同步至 Apache APISIX 中,完成服务的代理。</p> <p><img src="https://static.apiseven.com/202108/1630404265190-585b9b09-72d5-4320-b0fe-9cf8a73c55ea.png" alt="KubeSphere APISIX Ingress Controller demo" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1630404325747-b92928dc-2c6b-4574-a49d-32b6bcb187f9.png" alt="KubeSphere APISIX Ingress Controller demo" referrerpolicy="no-referrer"></p> <p><strong>注意</strong>我们添加了 <code>kubernetes.io/ingress.class: apisix</code> 的 annotation 配置,用于支持集群内多 ingress-controller 的场景。</p> <p>保存后,可看到如下界面:</p> <p><img src="https://static.apiseven.com/202108/1630404366474-dfe8ae08-f16d-417f-8ef3-3495ebda0f7d.png" alt="KubeSphere APISIX Ingress Controller demo" referrerpolicy="no-referrer"></p> <p>在终端下测试是否代理成功:</p> <pre><code class="language-shell">root@apisix:~$ curl http://172.18.0.5:31441/get -H "HOST: http-ing.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "http-ing.org", "User-Agent": "curl/7.76.1", "X-Forwarded-Host": "http-ing.org" }, "origin": "10.244.2.1", "url": "http://http-ing.org/get"} </code></pre> <p>可以看到也正常代理了。</p> <p>除了以上方式外,Apache APISIX Ingress Controller 通过 CRD 的方式对 Kubernetes 进行了扩展,你也可以通过发布 <code>ApisixRoute</code> 等这种自定义资源来完成 Kubernetes 中服务的对外暴露。</p> <h2>总结</h2> <p>你可以在 KubeSphere 中使用 Apache APISIX 的官方 Helm 仓库直接部署 Apache APISIX 和 APISIX Ingress Controller 。并且 Apache APISIX 可通过作为网关,或者 APISIX Ingress Controller 的数据面来承载业务流量。</p> <h2>未来展望</h2> <p>Apache APISIX 已经与 KubeSphere 社区达成合作,你可以直接在 KubeSphere 自带的应用仓库中找到 Apache APISIX ,不需要手动添加 Helm 仓库。</p>

使用 Apache APISIX 的 OpenID Connect 插件进行集中身份认证

<h2>关于作者</h2> <p><a href="https://github.com/starsz">@朱欣欣</a> 是<a href="https://www.apiseven.com/">API7.ai</a>的系统工程师,也是 Apache APISIX 项目的 committer。他是一个开源爱好者,也是一个 Golang 开发者。现在他正在为 Apache APISIX 的 control plane 贡献代码并执行 code review。除此之外,他在 Apache APISIX 社区中非常活跃,乐于回答社区内的各种问题。</p> <h2>什么是 Apache APISIX</h2> <p><a href="https://apisix.apache.org/">Apache APISIX</a> 是一个动态、实时、高性能的 API 网关,提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。Apache APISIX 不仅支持插件动态变更和热插拔,而且拥有众多实用的插件。Apache APISIX 的 OpenID Connect 插件支持 OpenID,用户可以使用该插件将身份认证从传统认证模式替换为集中认证模式。</p> <h2>什么是身份认证</h2> <p>身份认证是指通过一定的手段,对用户的身份进行验证。应用通过身份认证识别用户身份,并根据用户身份 ID 从身份提供方(Identity Provider)获取详细的用户元数据,并以此判断用户是否拥有访问指定资源的权限。身份认证模式分为两大类:<strong>传统认证模式</strong>和<strong>集中认证模式</strong>。</p> <h3>传统认证模式</h3> <p>在传统认证模式下,各个应用服务需要单独支持身份认证,例如当用户未登录时访问登录接口,接口返回 301 跳转页面。应用需要开发维护 Session 以及和身份提供商的认证交互等逻辑。传统认证模式的流程如下图所示:首先由用户发起请求(request),然后由网关接收请求并将其转发至对应的应用服务,最后由应用服务与身份提供方对接,完成身份认证(authorization)。</p> <p><img src="https://static.apiseven.com/202108/1629945145686-2a5d247b-b42e-43e6-b5fe-a1a716f514f1.png" alt="传统认证模式流程图" referrerpolicy="no-referrer"></p> <p>与传统认证模式不同,集中身份认证模式把用户认证从应用服务中抽离了出来,以 Apache APISIX 为例,集中认证的流程如下图所示:首先由用户发起请求(request),然后由前置的网关负责用户认证流程,与身份提供方对接,向身份提供方发送身份认证(authorization)请求。身份提供方返回用户身份信息(user info)。网关完成用户身份识别后,将用户身份信息通过请求头的形式转发至后端应用。</p> <p><img src="https://static.apiseven.com/202108/1629948254470-6eea722c-4567-4489-9a87-04b5673b6796.png" alt="集中认证模式流程图" referrerpolicy="no-referrer"></p> <p>相比较传统认证模式,集中认证模式下有如下优点:</p> <ol> <li>简化应用开发流程,降低开发应用工作量和维护成本,避免各个应用重复开发身份认证代码。</li> <li>提高业务的安全性,集中身份认证模式在网关层面能够及时拦截未经身份认证的请求,保护后端的应用。</li> </ol> <h2>什么是 OpenID</h2> <p>OpenID 是一种集中认证模式,它是一个去中心化的身份认证系统。使用 OpenID 的好处是,用户只需要在一个 OpenID 身份提供方的网站上注册和登录,使用一份账户密码信息即可访问不同应用。Okta 是一个常见的 OpenID 身份提供方,Apache APISIX OpenID Connect 插件支持 OpenID,所以用户可以使用该插件将传统认证模式替换为集中认证模式。</p> <h3>OpenID 认证过程</h3> <p>OpenID 认证过程有以下 7 个步骤,如下图所示。</p> <ol> <li>APISIX 向 Identity Provider 发起认证请求。</li> <li>用户在 Identity Provider 上登录并认证身份。</li> <li>Identity Provider 携带 Authorization Code 返回 APISIX。</li> <li>APISIX 使用从请求参数中提取到的 Code 请求 Identity Provider。</li> <li>Identity Provider 向 APISIX 发送应答消息,里面包含了 ID Token 和 Access Token。</li> <li>APISIX 将 Access Token 发送到 Identity Provider 的 User Endpoint,以进行获取用户身份。</li> <li>通过认证后,User Endpoint 将 User info 发送到 APISIX,完成身份验证。</li> </ol> <p><img src="https://static.apiseven.com/202108/1629945145694-58b0d80d-5ac7-4ff1-83ce-abcf2c5d13f7.png" alt="OpenID 认证流程图" referrerpolicy="no-referrer"></p> <h2>如何使用 Apache APISIX 的 OpenID Connect 插件配置 Okta 认证</h2> <p>使用 Apache APISIX OpenID Connect 插件配置 Okta 认证的过程非常简单,只需三步即可完成 Okta 配置 ,从传统身份认证模式切换到集中身份认证模式。下文讲述了使用 Apache APISIX 的 OpenID Connect 插件配置 Okta 认证的操作步骤。</p> <h3>前提条件</h3> <p>已有 Okta 账号。</p> <h3>步骤一:配置 Okta</h3> <ol> <li>登录你的 Okta 账号,并创建一个 Okta 应用,选择 OIDC 登录模式以及 Web Application 应用类型。 <img src="https://static.apiseven.com/202108/1629945284704-056f75ef-f726-4c62-8c4e-f491ba9ed35b.png" alt="创建一个 Okta 应用" referrerpolicy="no-referrer"> <img src="https://static.apiseven.com/202108/1629945322972-0a028f73-2cd8-4a84-9849-3e5d4f5c457b.png" alt="选择 OIDC 登录模式以及 Web Application 应用类型" referrerpolicy="no-referrer"></li> <li>设置登录和登出的跳转 URL。 其中 “Sign-in redirect URIs” 为登录成功允许跳转的链接地址,“Sign-out redirect URIs” 表示登出之后跳转的链接地址。在这个示例中,我们将登录成功跳转和登出之后跳转的链接地址都设置为 <code>http://127.0.0.1:9080/</code>。 <img src="https://static.apiseven.com/202108/1629945355148-237e1fb7-f874-4a61-adeb-0e062f40cb03.png" alt="设置登录和登出的跳转 URL" referrerpolicy="no-referrer"></li> <li>完成设置以后,单击“Save”保存修改。 <img src="https://static.apiseven.com/202108/1629945383145-18cfbe84-4c7f-4f62-8979-fa2679d96ee1.png" alt="保存修改" referrerpolicy="no-referrer"></li> <li>访问应用的 General 页面,获取以下配置,配置 Apache APISIX OpenID Connect 时需要提供这些信息:</li> </ol> <ul> <li>Client ID:OAuth client ID,即应用的 ID,与下文的 <code>client_id</code> 和 <code>{YOUR_CLIENT_ID}</code> 对应。</li> <li>Client secret:OAuth client secret,即应用密钥,与下文的 <code>client_secret</code> 和 <code>{YOUR_CLIENT_SECRET}</code> 对应。</li> <li>Okta domain:应用使用的域名,与下文的 discovery 中的 <code>{YOUR_ISSUER}</code> 对应。</li> </ul> <p><img src="https://static.apiseven.com/202108/1629945708891-d0e9e0bd-dd3a-4ce7-b497-7a31f6da3f49.png" alt="获取配置信息" referrerpolicy="no-referrer"></p> <h3>安装 Apache APISIX</h3> <p>你可以通过源码包、Docker、Helm Chart 等多种方式来安装 Apache APISIX。</p> <h4>安装依赖</h4> <p>Apache APISIX 的运行环境需要依赖 NGINX 和 etcd,所以在安装 Apache APISIX 前,请根据您使用的操作系统安装对应的依赖。我们提供了 CentOS7、Fedora 31 &amp; 32 、Ubuntu 16.04 &amp; 18.04、 Debian 9 &amp; 10 和 MacOS 上的依赖安装操作步骤,详情请参考<a href="https://apisix.apache.org/zh/docs/apisix/install-dependencies/">安装依赖</a>。</p> <p>通过 Docker 或 Helm Chart 安装 Apache APISIX 时,已经包含了所需的 NGINX 和 etcd,请参照各自对应的文档。</p> <h4>通过 RPM 包安装(CentOS 7)</h4> <p>这种安装方式适用于 CentOS 7 操作系统,请运行以下命令安装 Apache APISIX。</p> <pre><code class="language-shell">sudo yum install -y https://github.com/apache/apisix/releases/download/2.7/apisix-2.7-0.x86_64.rpm </code></pre> <h4>通过 Docker 安装</h4> <p>详情请参考:<a href="https://hub.docker.com/r/apache/apisix">使用 Docker 安装 Apache APISIX</a>。</p> <h4>通过 Helm Chart 安装</h4> <p>详情请参考:<a href="https://github.com/apache/apisix-helm-chart">使用 Helm Chart 安装 Apache APISIX</a>。</p> <h4>通过源码包安装</h4> <ol> <li>创建一个名为 <code>apisix-2.7</code> 的目录:</li> </ol> <pre><code class="language-shell">mkdir apisix-2.7 </code></pre> <ol> <li>下载 Apache APISIX Release 源码包:</li> </ol> <pre><code class="language-shell">wget https://downloads.apache.org/apisix/2.7/apache-apisix-2.7-src.tgz </code></pre> <p>您也可以通过 Apache APISIX 官网下载 Apache APISIX Release 源码包。 Apache APISIX 官网也提供了 Apache APISIX、APISIX Dashboard 和 APISIX Ingress Controller 的源码包,详情请参考 <a href="https://apisix.apache.org/zh/downloads">Apache APISIX 官网-下载页</a>。</p> <ol> <li>解压 Apache APISIX Release 源码包:</li> </ol> <pre><code class="language-shell">tar zxvf apache-apisix-2.7-src.tgz -C apisix-2.7 </code></pre> <ol> <li>安装运行时依赖的 Lua 库:</li> </ol> <pre><code class="language-shell"># 切换到 apisix-2.7 目录 cd apisix-2.7 # 创建依赖 make deps </code></pre> <h4>初始化依赖</h4> <p>运行以下命令初始化 NGINX 配置文件和 etcd。</p> <pre><code class="language-shell"># initialize NGINX config file and etcd make init </code></pre> <h3>启动 Apache APISIX 并配置对应的路由</h3> <ol> <li> <p>运行以下命令,启动 Apache APISIX。</p> </li> <li> <p>创建路由并配置 OpenID Connect 插件。</p> </li> </ol> <p>OpenID Connect 配置列表如下:</p> <table> <thead> <tr> <th style="text-align:left">字段</th> <th style="text-align:left">默认值</th> <th style="text-align:left">描述</th> </tr> </thead> <tbody> <tr> <td style="text-align:left">client_id</td> <td style="text-align:left">""</td> <td style="text-align:left">OAuth 客户端 ID</td> </tr> <tr> <td style="text-align:left">client_secret</td> <td style="text-align:left">""</td> <td style="text-align:left">OAuth 客户端密钥</td> </tr> <tr> <td style="text-align:left">discovery</td> <td style="text-align:left">""</td> <td style="text-align:left">身份提供商的服务发现端点</td> </tr> <tr> <td style="text-align:left">scope</td> <td style="text-align:left">openid</td> <td style="text-align:left">需要访问资源范围</td> </tr> <tr> <td style="text-align:left">relm</td> <td style="text-align:left">apisix</td> <td style="text-align:left">指定 WWW-Authenticate 响应头验证信息</td> </tr> <tr> <td style="text-align:left">bearer_only</td> <td style="text-align:left">false</td> <td style="text-align:left">是否检查请求头中的 token</td> </tr> <tr> <td style="text-align:left">logout_path</td> <td style="text-align:left">/logout</td> <td style="text-align:left">登出的 URI</td> </tr> <tr> <td style="text-align:left">redirect_uri</td> <td style="text-align:left">request_uri</td> <td style="text-align:left">身份提供商跳转回来的 URI,默认为请求地址</td> </tr> <tr> <td style="text-align:left">timeout</td> <td style="text-align:left">3</td> <td style="text-align:left">请求超时时间,单位为秒</td> </tr> <tr> <td style="text-align:left">ssl_verify</td> <td style="text-align:left">false</td> <td style="text-align:left">是否身份提供商的校验 ssl 证书</td> </tr> <tr> <td style="text-align:left">introspection_endpoint</td> <td style="text-align:left">""</td> <td style="text-align:left">身份提供商的令牌验证端点的 URL,不填则将从 discovery 响应中提取</td> </tr> <tr> <td style="text-align:left">introspection_endpoint_auth_method</td> <td style="text-align:left">client_secret_basic</td> <td style="text-align:left">令牌自省的认证方法名称</td> </tr> <tr> <td style="text-align:left">public_key</td> <td style="text-align:left">""</td> <td style="text-align:left">验证令牌的公钥</td> </tr> <tr> <td style="text-align:left">token_signing_alg_values_expected</td> <td style="text-align:left">""</td> <td style="text-align:left">验证令牌的算法</td> </tr> <tr> <td style="text-align:left">set_access_token_header</td> <td style="text-align:left">true</td> <td style="text-align:left">是否在请求头中携带 access token</td> </tr> <tr> <td style="text-align:left">access_token_in_authorization_header</td> <td style="text-align:left">false</td> <td style="text-align:left">true 时将 access token 放置在 Authorization 头中,false 时将 access token 放置在 X-Access-Token 头中</td> </tr> <tr> <td style="text-align:left">set_id_token_header</td> <td style="text-align:left">true</td> <td style="text-align:left">是否将 ID token 携带至 X-ID-Token 请求头</td> </tr> <tr> <td style="text-align:left">set_userinfo_header</td> <td style="text-align:left">true</td> <td style="text-align:left">是否将用户信息携带至 X-Userinfo 请求头</td> </tr> </tbody> </table> <p>以下代码示例通过 Apache APISIX Admin API 进行创建路由,设置路由的上游为 httpbin.org。httpbin.org 是一个简单的用于接收请求和响应请求的后端服务,下文将使用 httpbin.org 的 get 页面,参考 <a href="http://httpbin.org/#/HTTP_Methods/get_get">http bin get</a>。</p> <p>具体配置项请参考 <a href="https://apisix.apache.org/zh/docs/apisix/plugins/openid-connect/">Apache APISIX OpenID Connect Plugin</a>。</p> <pre><code class="language-shell">curl -XPOST 127.0.0.1:9080/apisix/admin/routes -H "X-Api-Key: edd1c9f034335f136f87ad84b625c8f1" -d '{ "uri":"/*", "plugins":{ "openid-connect":{ "client_id":"{YOUR_CLIENT_ID}", "client_secret":"{YOUR_CLIENT_SECRET}", "discovery":"https://{YOUR_ISSUER}/.well-known/openid-configuration", "scope":"openid profile", "bearer_only":false, "realm":"master", "introspection_endpoint_auth_method":"client_secret_post", "redirect_uri":"http://127.0.0.1:9080/" } }, "upstream":{ "type":"roundrobin", "nodes":{ "httpbin.org:80":1 } } }' </code></pre> <h3>步骤四:访问 Apache APISIX</h3> <ol> <li>访问 http://127.0.0.1:9080/get ,因为开启了 OpenID Connect 插件,所以页面被重定向到 Okta 登录页面。</li> </ol> <p><img src="https://static.apiseven.com/202108/1629945757074-1340330d-1429-4f3f-bc8e-c89153acaec5.png" alt="visit Okta login page" referrerpolicy="no-referrer"></p> <ol> <li> <p>输入用户在 Okta 注册的账号密码,单击“Sign in”,登录 Okta 账户。</p> </li> <li> <p>登录成功之后,能成功访问到 httpbin.org 中的 get 页面。该 httpbin.org/get 页面将返回请求的数据如下:</p> </li> </ol> <pre><code class="language-shell">"X-Access-Token": "******Y0RPcXRtc0FtWWVuX2JQaFo1ZVBvSlBNdlFHejN1dXY5elV3IiwiYWxnIjoiUlMyNTYifQ.***TVER3QUlPbWZYSVRzWHRxRWh2QUtQMWRzVDVGZHZnZzAiLCJpc3MiOiJodHRwczovL3FxdGVzdG1hbi5va3RhLmNvbSIsImF1ZCI6Imh0dHBzOi8vcXF0ZXN0bWFuLm9rdGEuY29tIiwic3ViIjoiMjgzMDE4Nzk5QHFxLmNvbSIsImlhdCI6MTYyODEyNjIyNSwiZXhwIjoxNjI4MTI5ODI1LCJjaWQiOiIwb2ExMWc4ZDg3TzBGQ0dYZzY5NiIsInVpZCI6IjAwdWEwNWVjZEZmV0tMS3VvNjk1Iiwic2NwIjpbIm9wZW5pZCIsInByb2Zpb***.****iBshIcJhy8QNvzAFD0fV4gh7OAdTXFMu5k0hk0JeIU6Tfg_Mh-josfap38nxRN5hSWAvWSk8VNxokWTf1qlaRbypJrKI4ntadl1PrvG-HgUSFD0JpyqSQcv10TzVeSgBfOVD-czprG2Azhck-SvcjCNDV-qc3P9KoPQz0SRFX0wuAHWUbj1FRBq79YnoJfjkJKUHz3uu7qpTK89mxco8iyuIwB8fAxPMoXjIuU6-6Bw8kfZ4S2FFg3GeFtN-vE9bE5vFbP-JFQuwFLZNgqI0XO2S7l7Moa4mWm51r2fmV7p7rdpoNXYNerXOeZIYysQwe2_L****", "X-Id-Token": "******aTdDRDJnczF5RnlXMUtPZUtuSUpQdyIsImFtciI6WyJwd2QiXSwic3ViIjoiMDB1YTA1ZWNkRmZXS0xLdW82OTUiLCJpc3MiOiJodHRwczpcL1wvcXF0ZXN0bWFuLm9rdGEuY29tIiwiYXVkIjoiMG9hMTFnOGQ4N08wRkNHWGc2OTYiLCJuYW1lIjoiUGV0ZXIgWmh1IiwianRpIjoiSUQuNGdvZWo4OGUyX2RuWUI1VmFMeUt2djNTdVJTQWhGNS0tM2l3Z0p5TTcxTSIsInZlciI6MSwicHJlZmVycmVkX3VzZXJuYW1lIjoiMjgzMDE4Nzk5QHFxLmNvbSIsImV4cCI6MTYyODEyOTgyNSwiaWRwIjoiMDBvYTA1OTFndHAzMDhFbm02OTUiLCJub25jZSI6ImY3MjhkZDMxMWRjNGY3MTI4YzlmNjViOGYzYjJkMDgyIiwiaWF0IjoxNjI4MTI2MjI1LCJhdXRoX3RpbWUi*****", "X-Userinfo": "*****lfbmFtZSI6IlpodSIsImxvY2FsZSI6ImVuLVVTIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiMjgzMDE4Nzk5QHFxLmNvbSIsInVwZGF0ZWRfYXQiOjE2MjgwNzA1ODEsInpvbmVpbmZvIjoiQW1lcmljYVwvTG9zX0FuZ2VsZXMiLCJzdWIiOiIwMHVhMDVlY2RGZldLTEt1bzY5NSIsImdpdmVuX25hbWUiOiJQZXRlciIsIm5hbWUiOiJQZXRl****" </code></pre> <p>其中:</p> <p><strong>X-Access-Token</strong>:Apache APISIX 将从用户提供商获取到的 access token 放入 X-Access-Token 请求头,可以通过插件配置中的 access_token_in_authorization_header 来选择是否放入 Authorization 请求头中。</p> <p><img src="https://static.apiseven.com/202108/1629945782309-14f84210-6cf3-4618-9ed0-809701094872.png" alt="X-Access-Token" referrerpolicy="no-referrer"></p> <p><strong>X-Id-Token</strong>:Apache APISIX 将从用户提供商获取到的 ID token 通过 base64 编码之后放入 X-Id-Token 请求头,可以通过插件配置中的 set_id_token_header 来选择是否开启该功能,默认为为开启状态。</p> <p><img src="https://static.apiseven.com/202108/1629945819163-8751bf83-57a1-4278-8fbf-5379e6e81c58.png" alt="X-Id-Token" referrerpolicy="no-referrer"></p> <p><strong>X-Userinfo</strong>: Apache APISIX 将从用户提供商获取到的用户信息,通过 base64 编码之后放入 X-Userinfo,你可以通过插件配置中的 set_userinfo_header 来选择是否开启该功能,默认为开启状态。</p> <p><img src="https://static.apiseven.com/202108/1629945855914-012fbb0a-d65f-428e-8f46-621291ff9deb.png" alt="X-Userinfo" referrerpolicy="no-referrer"></p> <p>由此可以看到,Apache APISIX 将会携带 X-Access-Token,X-Id-Token,X-Userinfo 三个请求头传递至上游。上游可以通过解析这几个头部,从而获取到用户 ID 信息和用户的元数据。</p> <p>我们展示了在 Apache APISIX 中直接建立来自 Okta 的集中式身份认证的过程。只要注册一个免费的 Okta 开发者账户就可以轻松开始了。这种集中认证的方法减少了开发者的学习和维护成本,提供了安全和精简的用户体验。</p> <h2>关于 Okta</h2> <p>Okta 是一个可定制的、安全的集中身份认证解决方案。Okta 可以为您的应用程序添加认证和授权。不需要自己编写代码,即可在您的应用程序中直接获得可扩展的认证。您可以将应用程序连接到 Okta,并定义用户的登录方式。每次用户尝试认证时,Okta 都会验证他们的身份,并将所需信息发回给您的应用程序。</p> <h2>关于 Apache APISIX</h2> <p>Apache APISIX 是一个动态、实时、高性能的 API 网关,提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。你可以使用 Apache APISIX 处理传统的南北向流量,以及服务间的东西向流量,也可以当做 <a href="https://github.com/apache/apisix-ingress-controller">Kubernetes Ingress Controller</a> 来使用。</p> <p>全球已有数百家企业使用 Apache APISIX 处理关键业务流量,涵盖金融、互联网、制造、零售、运营商等等,比如美国航空航天局(NASA)、欧盟的数字工厂、中国航信、中国移动、腾讯、华为、微博、网易、贝壳找房、360、泰康、奈雪的茶等等。</p> <p>Github:https://github.com/apache/apisix</p> <p>官网:https://apisix.apache.org</p>

为什么 Apache APISIX 选择 NGINX + Lua 这个技术栈?

<p>笔者在今年的 COSCUP 大会做分享时,曾有观众问这样的问题,为什么 <a href="https://github.com/apache/apisix">Apache APISIX</a>、Kong 和 3scale 这些网关都采用 Lua 来编写逻辑?</p> <p>是啊,Lua 并不是一门广为人知的语言,离“主流编程语言”的圈子大概还差个十万八千里吧。甚至有一次,我在跟别人交流的时候,对方在说到 Lua 之前,先停顿了片刻,随后终于打定主意,以"L U A"逐个字母发音的方式表达了对这一罕见之物的称呼。</p> <p>所以,为什么 Apache APISIX 和其他知名的网关会选择用 Lua 呢?</p> <p>事实上,Apache APISIX 采用的技术栈并不是纯粹的 Lua,准确来说,应该是 Nginx + Lua。Apache APISIX 以底下的 Nginx 为根基,以上层的 Lua 代码为枝叶。</p> <h2>LuaJIT VS Go</h2> <p>严谨认真的读者必然会指出,Apache APISIX 并非基于 Nginx + Lua 的技术栈,而是 Nginx + LuaJIT (又称 OpenResty,以下为了避免混乱,会仅仅采用 Nginx + Lua 这样的称呼)。</p> <p>LuaJIT 是 Lua 的一个 JIT 实现,性能比 Lua 好很多,而且额外添加了 FFI 的功能,能方便高效地调用 C 代码。 由于现行的主流 API 网关,如果不是基于 OpenResty 实现,就是使用 Go 编写,所以时不时会看到各种 Go 和 Lua 谁的性能更好的比较。</p> <p><strong>就我个人观点看,脱离场景比较语言的性能,是没有意义的。</strong></p> <p>首先明确一点,Apache APISIX 是基于 Nginx + Lua 的技术栈,只是外层代码用的是 Lua。所以如果要论证哪种网关性能更好,正确的比较对象是 C + LuaJIT 跟 Go 的比较。网关的性能的大头,在于代理 HTTP 请求和响应,这一块的工作主要是 Nginx 在做。</p> <p><strong>所以倘若要比试比试性能,不妨比较 Nginx 和 Go 标准库的 HTTP 实现。</strong></p> <p>众所周知,Nginx 是一个 bytes matter 的高性能服务器实现,对内存使用非常抠门。举两个例子:</p> <ol> <li>Nginx 里面的 request header 在大多数时候都只是指向原始的 HTTP 请求数据的一个指针,只有在修改的时候才会创建副本。</li> <li>Nginx 代理上游响应时对 buffer 的复用逻辑非常复杂,是我读过的最为烧脑的代码之一。</li> </ol> <p>凭借这种抠门,Nginx 得以屹立在高性能服务器之巅。</p> <p>相反的,Go 标准库的 HTTP 实现,是一个滥用内存的典型反例。</p> <p>这可不是我的一面之辞,Fast HTTP,一个重新实现 Go 标准库里面的 HTTP 包的项目,就举了两个例子:</p> <ol> <li>标准库的 HTTP Request 结构体没法复用</li> <li>headers 总是被提前解析好,存储成 map[string][]string,即使没有用到(原文见:<a href="https://github.com/valyala/FastHTTP#faq">Fast HTTP 常见文件</a> )</li> </ol> <p>Fast HTTP 文档里面还提到一些 bytes matter 的优化技巧,建议大家可以阅读下。</p> <p>事实上,即使不去比较作为网关核心的代理功能,用 LuaJIT 写的代码不一定比 Go 差多少。原因有二。</p> <p><strong>其一,拜 Lua 跟 C 良好的亲和力所赐,许多 Lua 的库核心其实是用 C 写的。</strong></p> <p>比如 lua-cjson 的 json 编解码,lua-resty-core 的 base64 编解码,实际上大头是用 C 实现的。 而 Go 的库,当然是大部分用 Go 实现的。虽然有 CGO 这种东西,但是受限于 Go 的协程调度和工具链的限制,它在 Go 的生态圈里面只能处于从属的地位。</p> <p>关于 LuaJIT 和 Go 对于 C 的亲和力的比较,推荐 Hacker News 上的这篇文章:《Comparing the C FFI overhead in various programming languages》(链接 https://news.ycombinator.com/item?id=17161168 )</p> <p>于是我们比较 Lua 的某些功能,其实还是会回到 C 和 Go 的比较中。</p> <p><strong>其二,LuaJIT 的 JIT 优化无出其右。</strong></p> <p>讨论动态语言的性能,可以把动态语言分成两类,带 JIT 和不带 JIT 的。JIT 优化能够把动态语言的代码在运行时编译成机器码,进而把原来的代码的性能提升一个数量级。</p> <p>带 JIT 的语言还可以分成两类,能充分 JIT 的和只支持部分 JIT 的。而 LuaJIT 属于前者。</p> <p><strong>人所皆知,Lua 是一门非常简单的语言。相对鲜为人知的是,LuaJIT 的作者 Mike Pall 是一个非常厉害的程序员。这两者的结合,诞生了 LuaJIT 这种能跟 V8 比肩的作品。</strong></p> <p>关于 LuaJIT 和 V8 到底谁更快,一直是长盛不衰的争论话题。展开讲 LuaJIT 的 JIT 已经超过了本文想要讨论的范畴。简单来说,JIT 加持的 LuaJIT 跟预先编译好的 Go 性能差别并不大。</p> <p>至于谁比谁慢,慢多少,那就是个见仁见智的问题了。这里我举个例子:</p> <pre><code class="language-Lua">local text = {"The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog", "at", "a", "restaurant", "near", "the", "lake", "of", "a", "new", "era"} local map = {} local times = 1e8 local n = #text for i = 1, n do map[text[i]] = 0 for _ = 1, times do map[text[i]] = map[text[i]] + 1 end end for i = 1, n do io.write(text[i], " ", map[text[i]], "\n") end </code></pre> <pre><code class="language-Go">package main import "fmt" func main() { text := []string{"The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog", "at", "a", "restaurant", "near", "the", "lake", "of", "a", "new", "era"} m := map[string]int{} times := int(1e8) for _, t := range text { m[t] = 0 for i := 0; i &lt; times; i++ { m[t]++ } } for _, t := range text { fmt.Println(t, " ", m[t]) } } </code></pre> <p>上面两段代码是等价的。你猜是第一个 Lua 版本的快,还是第二个 Go 版本的快?</p> <p>在我的机器上,第一个用时不到 1 秒,第二个花了 23 秒多。</p> <p>举这个例子并不是想证明 LuaJIT 比 Go 快 20 倍。我只想说明用 micro benchmark 证明某个语言比另一个语言快的意义不大,因为影响性能的因素很多。一个简单的 micro benchmark 很有可能过分强调某一个因素,导致出乎意料的结果。</p> <h2>Nginx + Lua :高性能 + 灵活</h2> <p>让我们转回 Apache APISIX 的 Nginx + Lua 的技术栈。Nginx + Lua 的技术栈给我们带来的,不仅仅是高性能。</p> <p>经常有人问我们,既然你们是基于 Nginx 开源版本,而 Nginx 并不支持动态配置,为什么 Apache APISIX 声称自己可以实现动态配置?你们是不是改了点东西?</p> <p>是的,我们确实有在维护自己的 Nginx 发行版,不过 Apache APISIX 的大部分功能在官方的 Nginx 上就能使用。我们之所以能做到动态配置,全靠把配置放到 Lua 代码里面来实现。</p> <p>举路由系统作为一个例子,Nginx 的路由需要在配置文件里面进行配置,每次更改路由,都需要 reload 之后才能生效。这是因为 Nginx 的路由分发只支持静态配置,不能动态增减路由。</p> <p><strong>为了实现路由动态配置,Apache APISIX 做了两件事:</strong></p> <ol> <li>在 Nginx 配置文件里面配置单个 server,这个 server 里面只有一个 location。我们把这个 location 作为主入口,这样所有的请求都会走到这个地方上来。</li> <li>我们用 Lua 完成路由分发的工作。Apache APISIX 的路由分发模块,支持在运行时增减路由,这样就能动态配置路由了。</li> </ol> <p>你可能会问,在 Lua 里面做路由分发,会比 Nginx 的实现慢吗?</p> <p>就像前面提到过的一样,凡是对性能要求比较高的,我们会把核心代码用 C 改写。我们的路由分发模块就是这么干的。路由分发模块在匹配路由时,会采用一个前缀树来匹配。而这个前缀树是用 C 实现的。感兴趣的读者可以看下代码:<a href="https://github.com/api7/lua-resty-radixtree/">lua-resty-radixtree</a>。</p> <p>完成了 C 层面上的前缀树匹配,接下来就该 Lua 发挥灵活性的时刻了。对于匹配同一前缀的各个路由,我们支持通过许多别的方式来进行下一级的匹配,其中就包含通过一个特定的表达式来匹配。尽管硬着头皮,也能在 C 层面上接入一个表达式引擎,但是纯 C 实现做不了非常灵活地自定义表达式里面的变量。</p> <p>举个例子,下面是 Apache APISIX 用来匹配 GraphQL 请求的 route 配置:</p> <pre><code class="language-json">{ "methods": [ "POST" ], "upstream": { "nodes": { "127.0.0.1:1980": 1 }, "type": "roundrobin" }, "uri": "/hello", "vars": [ [ "graphql_name", "==", "repo" ] ] } </code></pre> <p>它会匹配这样的 GraphQL 请求:</p> <pre><code class="language-Nginx">query repo { owner { name } } </code></pre> <p>这里的 graphql_name 并非 Nginx 内置变量,而是通过 Lua 代码定义的。Apache APISIX 一共定义了三个 GraphQL 相关的变量,连同解析 GraphQL body 在内不过 62 行 Lua 代码。如果要通过 Nginx C 模块来定义变量,62 行可能只不过是把相关方法的样板代码搭建起来,都还没有到真正的解析 GraphQL 的逻辑呢。</p> <p><strong>采用 Lua 代码来做路由还有一个好处:它减低了二次开发的门槛。</strong></p> <p>如果在路由过程中需要有特殊的逻辑,用户可以实现成自定义的变量和运算符,比如通过 IP 库匹配到的地理位置来决定采用哪条路由。用户只需要写一些 Lua 代码,这要比修改 Nginx C module 的难度小多了。</p> <p>在 Apache APISIX 里面,不仅仅路由是动态的,我们的 TLS 服务端证书和上游节点配置都是动态的,而且无需修改 Nginx —— 上述功能可以跑在官方的 Nginx + Lua 技术栈上。当然通过修改 Nginx,我们还实现了更多的高级功能,比如动态的 gzip 配置和动态的客户端请求大小限制。后续我们将推行自己的 Nginx 发行版,这样开源用户也能轻松用上这些高级功能。</p>

基于 Apache APISIX 的服务网格方案

<p>服务网格(Service Mesh)是处理服务间通信的基础设施层。它负责构成现代云原生应用程序的复杂服务拓扑来可靠地交付请求。通常会为每个服务实例提供一个“边车”(sidecar)代理实例。边车会处理服务间的通信、监控和安全相关的问题, 以及任何可以从各服务中抽象出来的逻辑。</p> <h2>Apache APISIX 简介</h2> <p>Apache APISIX 是一个动态、实时、高性能的 API 网关, 提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。</p> <h3>功能亮点</h3> <p>下面列举出 Apache APISIX 的部分功能亮点,更多功能特性请访问 <a href="https://github.com/apache/apisix">Github 仓库主页</a>。</p> <h4>全动态</h4> <p>Apache APISIX 是全动态的云原生 API 网关,上游、SSL 证书、路由、插件等所有功能,都支持动态更新,不需要 reload 就能实时生效,不会产生断连的情况。</p> <h4>丰富的负载均衡策略</h4> <p>Apache APISIX 支持比较多的负载均衡策略,如:最常用的带权轮询、一致性 hash、EWMA 算法(指数加权移动平均法)。</p> <h4>支持服务发现、服务治理</h4> <p>Apahce APISIX 提供服务发现的功能,如:可以和 Consul 和 Nacos 的组件对接,实现服务发现的功能。同时也支持服务治理相关的功能,如:限流、限速、熔断等。</p> <h4>扩展能力强</h4> <p>Apache APISIX 是完全基于插件进行扩展的,二次开发难度比较低,而且还能通过 Lua、Java、Go 和 WASM 等来开发自定义插件。借助灵活的插件机制,可针对内部业务完成功能定制。</p> <h3>架构</h3> <p><img src="https://static.apiseven.com/202108/1631065632487-3e46ee68-c1fb-4546-a82f-0f81504ce560.png" alt="Apache APISIX 的架构图" referrerpolicy="no-referrer"></p> <p>这是 Apache APISIX 的架构图,左边是数据面(Data Plane),右边是管理面(Control Plane),配置中心选择了当下最成熟的基础设施:etcd,通过 watch etcd,拿到路由、上游等数据。</p> <p>在这个架构里面,找不到一个单点,其中任何一个服务出现异常宕机等事故,都不会影响 APISIX 正常对外提供服务的能力。当整体架构中的每一个点都支持高可用时,用户生产系统的高稳定性就非常容易实现。</p> <h3>活跃的社区</h3> <p><img src="https://static.apiseven.com/202108/1631065708334-74c2c1bb-adf9-49a4-9008-4e04a815b27b.png" alt="APISIX 活跃状态呈现" referrerpolicy="no-referrer"></p> <p>Apache APISIX 有一个活跃健康的社区,截止到目前(主仓库)已经有 200 多个贡献者,和 Apache APISIX 相关的项目贡献者有近 300 人。Apache APISIX 自从 2019 年 6 月份开源以来,截止目前一共收到了 2200 多个 PR;坚持每个月发布一个版本,至今已经发布了 27 个版本。</p> <h2>Apache APISIX 服务网格方案</h2> <h3>为什么要使用服务网格?</h3> <p>各公司技术团队引入服务网格有各自的原因,如:公司里技术栈比较分散,需要在不同技术栈内完成同样的服务治理;需要维护多个版本,不同版本由不同团队维护,沟通成本比较高,这可能会带来一些差异性,可能引发一些故障。</p> <p>服务网格有一个非常重要的特性,就是服务治理。把限流限速、服务发现、服务注册、熔断等功能通过服务网格的方式下沉到基础组件,使业务研发人员不需要去关注服务治理,全神贯注实现业务层的开发,效率会提高,同时服务的稳定性也会提升。这是引入服务网格可能会带来的一些好处。</p> <h3>API 网关的优势</h3> <p>API 网关是 API 管理这个范畴的一个工具,除了把服务所提供的 API 暴露出来,供其他服务调用以外,也有一些和服务网格重叠的特性,如:服务治理 —— 服务发现、限流限速、熔断。同时还会在 API 网关层做认证、授权。这是 API 网关的一些优势。</p> <h3>基于 Apache APISIX 的服务网格</h3> <p>无论是使用服务网格,还是使用 API 网关,有一个共性原因是服务治理。既然二者有一些共性,那么 API 网关是否能够在服务网格里使用呢?Apache APISIX 是否能够胜任服务网格 sidecar 的角色呢?</p> <h4>设计方案前的调研</h4> <p>在 2021 年 1 月,我调研了全国十几家企业对服务网格的使用情况。这些企业中有一些正在做服务网格调研的,有些正在落地的,有些已经上生产环境了。调研结果总结归纳为以下三点:</p> <ol> <li>大家对于服务网格的使用和落地在国内处于早期阶段(仅业务落地,同时也有大量基于虚拟机的应用并存)。</li> <li>主要会使用到 HTTP 代理、灰度发布和蓝绿部署等功能 。</li> <li>关注服务的可观测性。</li> </ol> <p>对于服务网格,大家关注的大多是上述比较基础的功能。但是服务网格的功能是十分强大的,而大家只看到了其中一小部分核心功能。</p> <p>Apache APISIX 已经支持:</p> <ul> <li>HTTP 代理、gRPC 的代理</li> <li>TCP 、UDP 代理</li> <li>流量切分(灰度发布、蓝绿部署)</li> <li>负载均衡</li> <li>健康检查</li> <li>认证(mTLS、JWTToken)</li> <li>可观测性</li> </ul> <p>Apache APISIX 能与 Apache SkyWalking、Prometheus、ZipKin 等组件的结合,获取 metrics 和 tracing、 logging 的数据实现可观测性。</p> <p>由此可以看出 Apache APISIX 的功能满足服务网格数据面的要求。</p> <h4>Apache APISIX 作为数据面带来的问题</h4> <ul> <li><strong>问题一:如何及时获取配置变更?</strong></li> </ul> <p>在服务网格场景下,数据面实例可能有很多,如何保证在实例数特别多时配置变更的下发延时较低?</p> <ul> <li><strong>问题二:控制面的选型</strong></li> </ul> <p>一个完整的服务网格方案必须有数据面和控制面,Apache APISIX 可以作为数据面,控制面该怎么选呢?是选现有的服务网格产品,如:Istio / Kuma / OpenServiceMesh ,还是自研?</p> <ul> <li><strong>问题三:对 Apache APISIX 的改造成本</strong></li> </ul> <p>假设 Apache APISIX 需要做些修改才能在服务网格中使用,这些修改的范围有哪些?会不会太大?比如大到需要考虑 Lua 和 OpenResty 的生态。是否需要自研实现某些功能?自研过程中踩坑的成本有多大?</p> <p>如何及时获取配置变更?</p> <p><img src="https://static.apiseven.com/202108/1631065632487-3e46ee68-c1fb-4546-a82f-0f81504ce560.png" alt="Apache APISIX 的架构图" referrerpolicy="no-referrer"></p> <p>从 Apache APISIX 的架构图中可以看到,控制面是使用 etcd 作为配置中心的。</p> <p>etcd 是一个强一致性的分布式 KV 存储,使用 Raft 协议做分布式共识。使用 Raft 协议,每一个读写请求都需要过一遍 Raft 的状态机,比如:一个有 5 节点的 etcd 集群,至少需要 3 个节点都确认某次请求,这次请求中的数据才会提交成功。</p> <p>由于 Raft 算法特性使得组件的 QPS 不能太高,客户端连接数不能太多。在服务网格中,控制面如果使用 etcd 下发配置数据,可能就会把 etcd 集群冲垮,etcd 成为了解决问题的瓶颈。</p> <p>etcd Proxy 支持设置连接很多个客户端(比如一千或者更多),直接引入 etcd Proxy 组件是否能解决问题呢?答案是不能,因为 Apache APISIX 与 etcd 的通信协议是 HTTP 协议,依靠 etcd 集成的 gRPC gateway 把 HTTP 转成了 gRPC,etcd Proxy 不支持协议转换。</p> <p>在服务网格场景下,数据面实例可能有很多,为保证在实例数特别多时配置变更下发延时较低,只能通过对 etcd Proxy 组件二次开发来及解决延时问题,保证及时获取配置的变更。</p> <p><strong>控制面的选型</strong></p> <p>以下列出了现有的两类控制面:</p> <p>一类是 Istio、Kuma、OpenServiceMesh,这三个控制面的共性是数据面都是 Envoy,通过 xDS 协议把配置从控制面加载到数据面。</p> <p>另一类是 Linkerd,比较老牌的一个服务网格方案,数据面和控制面都是自研的。</p> <p>除了采用现有的控制面,还有一个方案就是自研。如果自研控制面,需要考虑很多方面。如:如何设计 CRDs (CustomResourceDefinition),能让管理员和控制面交互?如何解决数据从控制面下发到数据面?自研控制面的成本也是非常大的。</p> <p><strong>对 Apache APISIX 的改造成本</strong></p> <p>对 Apache APISIX 的改造成本涉及到 Apache APISIX 技术栈的生态。</p> <p>第一个要考虑的是 Lua 语言,因为 Lua 语音的使用者比较小众,虽然 Lua 很强大,但是对它生态和工具的支持都比较匮乏。</p> <p>第二个需要考虑的是 OpenResty,Apache APISIX 底层是基于 OpenResty 的,而 OpenResty 是通过将 LuaJIT 嵌入到 Nginx 里面,来实现其强大的动态能力,而因为 Nginx 社区不活跃,Lua 语言比较小众,导致 OpenResty 社区非常小,近几年社区也越来越不活跃。如果基于 OpenResty 做扩展,如:支持 xDS 协议,缺少 gRPC 的 IDL 翻译工具,需要 Envoy xDS 整套配置的 Lua 翻译,因为 Envoy 没有提供 Lua 版本的。</p> <h4>解决方案:apisix-mesh-agent</h4> <p>计算机科学有一句名言:计算机科学的任意一个问题都能通过引入一个额外的间接层来解决掉。</p> <p>如果只在数据面中使用 Apache APISIX,上面三个问题很难解决,而且使用起来可能有很多的问题。所以结论是:开发一个间接层。间接层是起名为 apisix-mesh-agent,现已经开源,点击跳转<a href="https://github.com/api7/apisix-mesh-agent">项目地址</a>。</p> <h2>apisix-mesh-agent 的功能与优势</h2> <p>apisix-mesh-agent 作为 Apache APISIX 和控制面的中间层,从控制面获取配置(变更),并模拟 etcd V3 API 使 Apache APISIX 获取到配置;并创建 iptables 规则劫持出入流量。</p> <p>接下来详细介绍下 apisix-mesh-agent 的功能与优势。</p> <h3>对接控制面</h3> <p>apisix-mesh-agent 支持 xDS 协议,这使得 Apache APISIX 能对接实现 xDS 协议的控制面,如:Istio。从控制面获得的配置格式也是 xDS 协议格式的,apisix-mesh-agent 把配置转换成 Apache APISIX 可识别的格式。使用 Apache APISIX 格式的配置有点好处:节省内存资源。因为 xDS 协议的字段比较冗余,Apache APISIX 的配置字段非常简洁,保存配置到内存,占用的内存资源比较少。</p> <h3>实现 etcd V3 API</h3> <p>etcd V3 API 有两类的接口,一类是 etcd 实例之间进行 Raft 协议数据传递的 API,另一类是面向客户端的 API。apisix-mesh-agent 实现的是面向客户端的 API,目前只实现了 Apache APISIX 必用 API,如:Range 、Watch。实现这些接口后,只需要在 Apache APISIX 的配置里将 etcd 的地址配置到 apisix-mesh-agent 的监听地址,Apache APISIX 不需要任何改动就可以把数据从 apisix-mesh-agent 存到内存里。</p> <p>通过这个间接层,数据就能够从服务网格控制面传递到 Apache APISIX。</p> <h3>架构设计</h3> <p><img src="https://static.apiseven.com/202108/1631065948150-c5dea6fe-469d-4246-b847-d4868407c64f.jpg" alt="基于 Apache APISIX 的服务网格架构图" referrerpolicy="no-referrer"></p> <p>架构是按照控制面和数据面来进行拆分的,架构图的上面是控制面,控制面可以选用 Istio、Kuma 等;架构图的下面是网格的数据面,每一个服务网格的数据面有三个 Pod, 每一个 Pod 的 instance 都有自己的 sidecar 容器,在这个容器里有两个进程,分别是 Apache APISIX 和对应的 apisix-mesh-agent。apisix-mesh-agent 和对应的控制面进行交互,及时获取数据,获取后转换成 Apache APISIX 能够解析的格式,Apache APISIX 和 apisix-mesh-agent 之间通过 etcd API 交互。如此一来 Apache APISIX 就能够及时获取配置的变更并生效。</p> <h3>优势</h3> <p>在服务网格里使用 Apache APISIX 有什么优势呢?</p> <p><strong>第一,Apache APISIX 自身的优势。</strong></p> <p>Apache APISIX 是一个高性能 API 网关,上面已经介绍过它的特性,此处不再赘述。值得一提的是 Apache APISIX 是使用 Radix Tree 实现路由匹配,路由匹配性能和路由的数量无关,无论有多少路由 Apache APISIX 都能够保证非常快速地匹配到路由,时间复杂度是常数级别的。</p> <p><strong>第二,复用 Apache APISIX 全部插件。</strong></p> <p>Apache APISIX 目前开源了 50 多个插件,使用 Apache APISIX 作为服务网格的数据面,可以复用这些插件。如果现有插件不能满足业务需求,也可以自行开发插件, 而且 Apache APISIX 支持多语言开发插件,如 Java 和 Go。</p> <p><strong>第三,降低运维成本。</strong></p> <p>如果南北向流量和东西向流量的技术栈无法统一,需要在东西向上使用 Envoy,在南北向上使用 Apache APISIX,运维团队需要同时维护 2 个组件,这对运维人员来说是一个挑战。</p> <p>使用基于 Apache APISIX 的服务网格方案,能够统一南北向和东西向的技术栈,只需要管理 1 个组件,大大降低了运维成本。</p> <h2>基于 Apache APISIX 的服务网格,规划与发展方向</h2> <p>截止目前,apisix-mesh-agent 开源仅两个月时间,还有许多功能要完善,以下是对 Apache APISIX 服务网格未来的一些规划。</p> <p><strong>第一,对 xDS 协议做更多支持</strong></p> <p>目前,apisix-mesh-agent 只支持了 xDS 协议中的 LDS、RDS、CDS 和 EDS,而且只使用 LDS 绑定RDS 需要的路由配置名称,直接获取路由;使用 CDS 和 EDS 直接获取服务集群的定义和服务集群的实例。</p> <p>未来我们计划:</p> <ul> <li>支持 Apache APISIX 更多数据类型的转换</li> <li>支持请求改写,如:URI 改写</li> <li>支持请求认证,如 JWT Token、mTLS</li> <li>支持故障注入</li> </ul> <p><strong>第二,提升可观测性</strong></p> <p>在调研阶段发现,实际使用场景中大家十分重视服务的可观测性,未来支持对接更多可观测方面的组件,如:Apache SkyWalking、其他日志收集的服务器。</p> <p><strong>第三,自研控制面</strong></p> <p>现在服务网格的控制面依托其他开源组件(如:Istio、Kuma),使得我们可以以较低的成本使用现有的功能,而如果未来遇到问题很难解决,也会考虑去落地实现一个专属 Apache APISIX 的服务网格控制面 ,使 Apache APISIX 的能力更好地发挥出来,服务网格在使用中也更加高效、简便。</p>

Go 让 Apache APISIX 如虎添翼

<h2>为什么是 Go</h2> <p>Apache APISIX 允许用户通过插件的方式来拓展功能,如鉴权、限流、请求改写等核心功能都是通过插件的方式实现的。虽然 Apache APISIX 核心代码是使用 Lua 编写的,但是 Apache APISIX 支持多语言开发插件,比如 Go 、Java。 这篇文章将详细讲解如何用 Go 来开发 Apache APISIX 插件。通过拥抱 Go 的生态圈,为 Apache APISIX 开创一片新天地,希望 Go 能让 Apache APISIX 如虎添翼!</p> <h2>安装</h2> <p>采用库的方式来使用 Go Runner,apisix-go-plugin-runner 中的 <code>cmd/go-runner</code> 官方给出的例子,展示该如何使用 Go Runner SDK。未来也会支持通过 Go Plugin 的机制加载预先编译好的插件。</p> <h2>开发</h2> <h3>使用 Go Runner SDK 进行开发</h3> <pre><code class="language-bash">$ tree cmd/go-runner cmd/go-runner ├── main.go ├── main_test.go ├── plugins │ ├── say.go │ └── say_test.go └── version.go </code></pre> <p>上面是官方示例的目录结构。<code>main.go</code> 是入口,其中最关键的部分在于:</p> <pre><code class="language-go">cfg := runner.RunnerConfig{} ... runner.Run(cfg) </code></pre> <p><code>RunnerConfig</code> 可以用来控制日志等级和日志输出位置。</p> <p><code>runner.Run</code> 会让应用监听目标位置,接收请求并执行注册好的插件。应用会一直处于这一状态直到退出。</p> <p>打开 <code>plugins/say.go</code>:</p> <pre><code class="language-go">func init() { err := plugin.RegisterPlugin(&amp;Say{}) if err != nil { log.Fatalf("failed to register plugin say: %s", err) } } </code></pre> <p>由于 <code>main.go</code> 导入了 plugins 包,</p> <pre><code class="language-go">import ( ... _ "github.com/apache/apisix-go-plugin-runner/cmd/go-runner/plugins" ... ) </code></pre> <p>这样就在执行 <code>runner.Run</code> 之前通过 <code>plugin.RegisterPlugin</code> 注册了 <code>Say</code>。</p> <p><code>Say</code> 需要实现以下方法:</p> <p><code>Name</code> 方法返回插件名。</p> <pre><code class="language-go">func (p *Say) Name() string { return "say" } </code></pre> <p><code>ParseConf</code> 会在插件配置变化的时候调用,解析配置并返回插件特定的配置上下文。</p> <pre><code class="language-go">func (p *Say) ParseConf(in []byte) (interface{}, error) { conf := SayConf{} err := json.Unmarshal(in, &amp;conf) return conf, err } </code></pre> <p>该插件的上下文是这样的:</p> <pre><code class="language-go">type SayConf struct { Body string `json:"body"` } </code></pre> <p><code>Filter</code> 会在每个配置了 say 插件的请求中执行。</p> <pre><code class="language-go">func (p *Say) Filter(conf interface{}, w http.ResponseWriter, r pkgHTTP.Request) { body := conf.(SayConf).Body if len(body) == 0 { return } w.Header().Add("X-Resp-A6-Runner", "Go") _, err := w.Write([]byte(body)) if err != nil { log.Errorf("failed to write: %s", err) } } </code></pre> <p>可以看到 Filter 把配置里面的 body 的值作为响应体。如果在插件中直接进行响应,就会中断请求。</p> <p>Go Runner SDK API 文档:https://pkg.go.dev/github.com/apache/apisix-go-plugin-runner</p> <p>把应用构建起来后(在示例里面是 <code>make build</code>),在运行时需要设置两个环境变量:</p> <ol> <li><code>APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock</code></li> <li><code>APISIX_CONF_EXPIRE_TIME=3600</code></li> </ol> <p>像这样:</p> <pre><code class="language-go">APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock APISIX_CONF_EXPIRE_TIME=3600 ./go-runner run </code></pre> <p>应用运行时会去监听 <code>/tmp/runner.sock</code>。</p> <h3>设置 Apache APISIX (开发)</h3> <p>首先要安装 Apache APISIX,需要和 Go Runner 位于同一实例上。</p> <p><img src="https://static.apiseven.com/202108/20210819001.png" alt="Apache APISIX work flow" referrerpolicy="no-referrer"></p> <p>上图左边是 Apache APISIX 的工作流程,右边的 plugin runner 负责运行不同语言编写的外部插件。apisix-go-plugin-runner 就是这样支持 Go 语言的 runner。</p> <p>当你在 Apache APISIX 中配置一个 plugin runner 时,Apache APISIX 会把 plugin runner 作为自己的一个子进程,该子进程与 Apache APISIX 进程属于同一个用户,当我们重启或重新加载 Apache APISIX 时,plugin runner 也将被重启。</p> <p>如果为一个给定的路由配置了 ext-plugin-* 插件,击中该路由的请求将触发 Apache APISIX 通过 unix socket 向 plugin runner 执行 RPC 调用。调用细分为两个阶段:</p> <ul> <li>ext-plugin-pre-req: 在执行绝大部分 Apache APISIX 内置插件(Lua 语言插件)之前</li> <li>ext-plugin-post-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之后</li> </ul> <p>根据需要配置 plugin runner 的执行时机。</p> <p>plugin runner 会处理 RPC 调用,在其内部创建一个模拟请求,然后运行其他语言编写的插件,并将结果返回给 Apache APISIX。</p> <p>这些插件的执行顺序是在 ext-plugin-* 插件配置项中定义的。像其他插件一样,它们可以被启用并在运行中重新定义。</p> <p>为了展示如何开发 Go 插件,我们先设置 Apache APISIX 进入开发模式。在 config.yaml 中增加以下配置:</p> <pre><code class="language-shell">ext-plugin: path_for_test: /tmp/runner.sock </code></pre> <p>这个配置的意思是,命中路由规则后,Apache APISIX 会向 /tmp/runner.sock 发起 RPC 请求。</p> <p>接下来设置路由规则:</p> <pre><code class="language-shell">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/get", "plugins": { "ext-plugin-pre-req": { "conf": [ {"name":"say", "value":"{\"body\":\"hello\"}"} ] } }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } } ' </code></pre> <p>注意插件名称配置在 <code>name</code> 里面,插件配置(经 JSON 序列化后)放在 <code>value</code> 里面。</p> <p>如果在开发过程中看到 Apache APISIX 端有 <code>refresh cache and try again</code> 的 warning 和 Runner 端有 <code>key not found</code> 的 warning,这是因为配置缓存不一致导致的。因为开发状态下,Runner 不是由 Apache APISIX 管理的,所以内部状态会有可能不一致。不用担心,Apache APISIX 会重试。</p> <p>然后我们请求一下:curl 127.0.0.1:9080/get</p> <pre><code class="language-shell">$ curl http://127.0.0.1:9080/get HTTP/1.1 200 OK Date: Mon, 26 Jul 2021 11:16:11 GMT Content-Type: text/plain; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive X-Resp-A6-Runner: Go Server: APISIX/2.7 hello </code></pre> <p>可以看到接口返回 hello 而且没有访问到任何上游。</p> <h3>设置 Apache APISIX (运行)</h3> <p>这里以 go-runner 为例,只需把运行命令行配置在 ext-plugin 里就可以运行了:</p> <pre><code class="language-shell">ext-plugin: # path_for_test: /tmp/runner.sock cmd: ["/path/to/apisix-go-plugin-runner/go-runner", "run"] </code></pre> <p>Apache APISIX 会把 plugin runner 作为自己的一个子进程,管理它的整个生命周期。</p> <p>注意:这时就不要配置 path_for_test 了。Apache APISIX 在启动 runner 时会自动分配一个 unix socket 地址供 runner 监听。APISIX_LISTEN_ADDRESS 和 APISIX_CONF_EXPIRE_TIME 这两个环境变量也不用手动设置。</p> <h2>总结</h2> <p>目前 Go Plugin Runner 还处于早期开发阶段,我们会陆续完善其功能。成功的开源项目离不开大家的贡献,欢迎各位参与到 apisix-go-plugin-runner 的开发中来,让我们一起共建 Apache APISIX 和 Go 的桥梁! 点击访问 <a href="https://github.com/apache/apisix-go-plugin-runner">apisix-go-plugin-runner</a>.</p> <h2>相关阅读</h2> <p><a href="https://apisix.apache.org/blog/2021/06/21/use-java-to-write-apache-apisix-plugins">如何用 Java 编写 Apache APISIX 插件</a></p>

贡献者,是衡量开源项目的金指标

<p>根据 GitHub 2020 年的统计显示,这一年新增了 6000 万个代码仓库,超过 5600 万的开发者参与到开源项目中。预计到 2025 年,在 GitHub 参与开源项目的开发者人数将会超过 1 亿。</p> <p>在这些快速增长的数字背后,隐藏的是开源项目为了赢得开发者而做出的各种宣传:硬核技术文章、社交活动、新媒体、周边礼物等,可以说是乱花渐欲迷人眼。那么对于开发者来说,如何能够在百花齐放的项目中,找出最适合自己公司的那一个呢?</p> <p>这就会涉及到开源项目的各种指标:</p> <ul> <li> <p>Star 数:这是最直接的指标,它代表的是这个项目吸引了多少开发者的关注,可以体现这个项目的 marketing 水平。如果这个项目有商业公司资金的支持、强力的 PR 团队,或者是水军刷 Star,那么这个指标就很容易失真;</p> </li> <li> <p>Issue 和 PR 数:GitHub 提供了 Insights 功能,如下图所示:</p> </li> </ul> <p><img src="https://static.apiseven.com/202108/20210814001.webp" alt="Apache APISIX GitHub Insights" referrerpolicy="no-referrer"></p> <p>可以选择最近一周、最近一个月的时间内,这个开源项目的 Issue 和 PR 的新建和关闭数。上图是 Apache APISIX 最近一个月的数据。</p> <p>GitHub insights 提供了一个非常棒的开发者视角,但还不够完美:Issue 和 PR 的质量如何?这些是没有办法量化的;</p> <ul> <li>Commit 频率和数据:下图是 Apache APISIX 从项目创建到现在的 Commit 频率统计,可以看出 Apache APISIX 保持了非常稳定和持续的开发,但这个指标也略显单薄:看不到提交这些 Commit 的开发者的数据;</li> </ul> <p><img src="https://static.apiseven.com/202108/20210814002.webp" alt="Apache APISIX commits" referrerpolicy="no-referrer"></p> <p>看到这里,你是不是觉得选择一个开源项目好复杂,看了这么多指标都得不出答案。有没有一个“金指标”,一个通过 marketing 砸不出来的指标?一个能够体现“开发者为核心”的指标?</p> <p>作为开源项目的维护者和开发者,我们也需要这样的金指标来指引我们。所以,我们提出了 “贡献者增长” 和 “活跃贡献者”这两个维度,并将统计和分析的过程开源出来:<a href="https://github.com/api7/contributor-graph">https://github.com/api7/contributor-graph</a>。你也可以通过 www.apiseven.com/contributor-graph 直接进行检索。下面是 Apache APISIX 的示例:</p> <h2>贡献者增长</h2> <p><img src="https://static.apiseven.com/202108/20210814003.webp" alt="Apache APISIX contributor growth" referrerpolicy="no-referrer"></p> <h2>月度活跃贡献者</h2> <p><img src="https://static.apiseven.com/202108/20210814004.webp" alt="Apache APISIX Monthly Active Contributors" referrerpolicy="no-referrer"></p> <p>通过上面两个表格,你可以清晰的看到 Apache APISIX 从创办至今,贡献者都保持着稳定的增长,每个月都有 25 个左右的代码贡献者参与其中。</p> <h2>多仓库对比</h2> <p>“贡献者增长” 和 “活跃贡献者”图表都支持在多仓库之间比较。更重要的是,我们会拉取 Github API 每日/每月定时更新图表,只要一次性的复制使用我们提供的链接,您的仓库就可以始终显示实时的贡献者数据。</p> <p>贡献者增长曲线的数据来源是项目每个 contributor 首次提交 commit 的日期。通过贡献者增长曲线,我们可以在 Github 首页显示的贡献者总人数基础上观察贡献者增长情况,从而对社区的发展情况作出直观判断。</p> <p>通过同时展示同一领域中的多个仓库,我们也可以直观比较不同社区的发展情况。</p> <p><img src="https://static.apiseven.com/202108/20210814005.webp" alt="Apache APISIX compare the development of different communities" referrerpolicy="no-referrer"></p> <p>在上图中,我们可以看到 Apache APISIX 的贡献者人数以非常快的速度增长,仅仅用了两年时间,贡献者人数就基本赶上甚至超越了其他开源网关项目。</p> <p>月度贡献者曲线数据则来自于每月提交 commit 的贡献者数量。</p> <p>相比贡献者增长曲线,月度贡献者可以更好衡量短时间内的社区发展情况。</p> <p><img src="https://static.apiseven.com/202108/20210814006.webp" alt="Apache APISIX monthly contributors compare" referrerpolicy="no-referrer"></p> <p>例如在上图中,我们可以看到如今 Apache APISIX 是多个开源网关项目中的月度贡献者人数最多、最稳定的。</p> <p>这也解释了为何 Apache APISIX 可以在开源后如此短的时间内在总贡献者人数上赶上友商们。</p> <p><img src="https://static.apiseven.com/202108/20210814007.webp" alt="Apache APISIX monthly contributors" referrerpolicy="no-referrer"></p> <p>上面这张比较开源消息中间件社区的月度贡献者的图,曾在推特引发热议。</p> <p>通过这张图,我们可以看到 Apache Pulsar 在月度贡献者上迎头赶上了 Apache Kafka。</p> <h2>总结</h2> <p>目前贡献者图表已经在 Apache APISIX 、Apache Skywalking、Apache DolphinScheduler、Apache Openwhisk、Apache ShardingSphere、awesome-docker、TiDB docs-dm 等多个开源项目中使用。</p> <p>在用户使用并反馈的过程中,我们实现了更多的需求,比如添加“匿名”贡献者,或是项目由 SVN 迁至 GitHub 后添加 SVN 侧的贡献者等。</p> <p>好程序都是起源于程序员要解决的切身之痛。当我们聊 Contributor Graph 时,我们不仅希望借助这个工具生产更加直观的展示 Apache APISIX 社区活跃度的图表,我们同时开源了这个小工具,希望这个工具可以帮助到其他的开源项目。</p> <p>欢迎大家使用贡献者趋势图表来跟踪您的社区活跃度,任何需求和问题都欢迎到 Contributor Graph 的 GitHub 仓库中反馈。</p> <p>您可以通过以下链接或者点击阅读原文,访问 Contributor Graph <a href="https://github.com/api7/contributor-graph">repository</a>。</p>

如何利用 Apache APISX 提升 Nginx 的可观测性

<h2>概述</h2> <p>“可观测性“是一种度量手段,方便掌握基础设施、系统平台或者应用程序的运行状况。常见的手段是收集 metrics、logging 和 tracing 及 events 数据,可以帮助开发人员和运维人员检测、调查、预警和纠正系统问题。</p> <p>本文将从 Nginx 可观测性、Apache APISIX 与 Nginx 的关系、Apache APISIX 可观测性,以及结合 Apache SkyWalking 进一步提升可观测性这些方面分享关于可观测性的方案与实践。</p> <h2>Nginx 的可观测性</h2> <h3>Nginx 常见监控方式</h3> <p>Nginx 在一定程度上能够做到可观测,以下罗列出 Nginx 的常见监控方式:</p> <ol> <li>ngx_http_stub_status_module</li> <li>VTS module + exporter + prometheus + grafana(如果 Nginx 版本比较低,需要引入 exporter )</li> <li>Nginx Amplify SaaS</li> </ol> <h4>ngx_http_stub_status_module</h4> <p>ngx_http_stub_status_module 主要收集实例级别的统计信息。</p> <h4>VTS module</h4> <p>VTS module 有 3 个比较明显的缺点:</p> <ol> <li> <p><strong>安装复杂</strong>:虽然 VTS module 能够采集指标,采集的指标类型也比较多,但是它的安装比较复杂。如果想要采用 VTS module,需要重新编译 Nginx,在编译 Nginx 之前加入 VTS moudle,完成编译后重新安装 Nginx 才可以正常使用 VTS。</p> </li> <li> <p><strong>扩展能力弱</strong>:VTS 扩展能力分为两部分,一是在编译之前给 VTS 增加扩展;二是在编译之后增加扩展 —— 修改 nginx.conf 配置文件。通过修改 nginx.conf 文件来增加扩展会使 Nginx reload,生产环境直接 reload 或多或少会对业务产生一些影响。</p> </li> <li> <p><strong>社区更新缓慢</strong>:VTS module 最新的一次更新是在 2018 年,已经停摆 3 年了。</p> </li> </ol> <h4>Nginx Amplify SaaS</h4> <p>Nginx Amplify 是一个 SaaS 服务,Nginx Amplify 在远端提供服务,在 Nginx 服务之外安装 Agent。 在 Nginx 之外安装采集模块,那么在采集指标上就会有限制,只能拿到 Nginx 暴露出来的信息,没有暴露的内部信息是拿不到的。</p> <p>另外,由于 Nginx Amplify SaaS 是一个 SaaS 服务,需要通过公网将采集到的数据传到服务端,这会带来一些安全隐患,同时把一些企业用户阻挡在外面。或许 Nginx Amplify 的目标群体是 Nginx plus 这样的企业用户,不是开源用户。</p> <p>另外,Nginx Amplify SaaS 社区也不活跃,已经停摆 2 年。</p> <h3>Nginx 的缺陷</h3> <p>Nginx 在 Events 收集上自身有缺陷,这里列举出两个问题:</p> <ol> <li> <p>Nginx 基于 nginx.conf 进行配置,修改后通过 reload nginx.conf 文件使配置生效。除 reload 事件外,没有其他 Events 可用,我们无法得知每次修改文件的变化,如:起初只配置 1 个路由,修改文件增加 10 个路由,只有 reload 事件无法得知增加的到低是哪 10 个路由。</p> </li> <li> <p>Nginx 开源产品缺少主动健康检查。Nginx 是一个反向代理,真正的后端服务可能会出现重启、升级或者异常的情况,如果没有主动的健康检查,依靠被动检查,只能在流量出现异常的时候,才知道服务出了问题,这会丢掉很多 Events,导致上游 Events 事件信息不完整。</p> </li> </ol> <h3>Nginx 可观测性总结</h3> <p>Nginx 的开源版本没有提供非常好用的监控。虽然 Nginx 提供了一些监控工具,但这些工具的安装和配置非常复杂,几乎没有扩展性。可能这些工具的设计并不是为了可观测性,只是为了能看到指标或统计数据,方便定位问题。现在有各种可观测性设置类的产品,但是很难集成到 Nginx 上。</p> <p>另外,Nginx 社区停滞不前,导致 Nginx 迭代速度慢。</p> <h2>Apache APISIX 概述</h2> <h3>Apache APISIX 与 Nginx 的关系</h3> <p><img src="https://static.apiseven.com/202108/1630399942878-58185f99-b2b6-44a1-8a15-34c49c21e1c2.png" alt="Apache APISIX 与 Nginx 的关系" referrerpolicy="no-referrer"></p> <p>Apache APISIX 基于 Nginx 实现,但只依赖 Nginx 的网络库,在 Nginx 基础上,Apache APISIX 实现了自己的核心的代码,并预留了扩展机制。</p> <p>在表格中列出了 Apache APISIX 和 Nginx 的功能对比,Apache APISIX 既可以做反向代理,又可以实现一些 Nginx 不支持的功能,如:主动健康检查、流量管理、横向扩缩容等,而且这些功能都是开源的。</p> <ul> <li><strong>API 设计</strong>:在 API 设计方面使用 Apache APISIX 更加简单。</li> <li><strong>开源 Dashboard</strong>:在界面上就能把反向代理全部配置完。</li> <li><strong>主动健康检查</strong>:Apache APISIX 支持主动健康检查,可以结合 Events 完善可观测性。</li> <li><strong>流量管理</strong>:适合监测数据,或者在业务发布上线时使用。</li> <li><strong>横向扩缩容</strong>:Apache APISIX 支持横向扩缩容,这个特性得益于 Apache APISIX 的架构(见下图)。</li> <li><strong>插件扩展机制</strong>:Apache APISIX 的插件扩展机制使其具有极为强大的扩展能力。</li> <li><strong>插件编排</strong>:按照业务需求,将多个插件按照逻辑编排,组合起来使用。</li> <li><strong>证书管理</strong>:Apache APISIX 支持动态的证书管理。</li> </ul> <p><img src="https://static.apiseven.com/202108/1630399942881-67d3ff6f-8f45-44e6-abe8-d2b7d30991b7.png" alt="Apache APISIX 架构图" referrerpolicy="no-referrer"></p> <h3>Apache APISIX 简介</h3> <p>Apache APISIX 是一个动态、实时、高性能的 API 网关,提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。Apache APISIX 也是全世界最活跃的开源 API 网关项目,是生产可用的高性能网关。全球已有数百家企业使用 Apache APISIX 处理关键业务流量,涵盖金融、互联网、制造、零售、运营商等等,比如美国航空航天局(NASA)、欧盟的数字工厂、中国航信、中国移动、腾讯、华为、微博、网易、贝壳找房、360、泰康等。</p> <h3>Apache APISIX 解决方案</h3> <p><img src="https://static.apiseven.com/202108/1630399942883-e0b5d8f5-ccbc-4bb1-bafd-171e511cc211.png" alt="Apache APISIX 全流量解决方案" referrerpolicy="no-referrer"></p> <p>上图左边,从上往下是从单体服务到 SOA(面向服务的架构)到微服务的演进过程。</p> <p>在 SOA 下,网关一般采用 Nginx 或者 HAProxy;在微服务架构下,网关使用 Nginx 做负载均衡。微服务架构有两个比较常见的解决方案:一个是基于 Java 技术栈实现,如 Spring Cloud 系列;另一个是 Service Mesh。</p> <p>在这个演进过程中,Apache APISIX 处于什么位置,能做什么呢?</p> <p>简单的说,左图中红色的部分(Nginx / HAProxy / Kong / Spring Cloud Zuul / Spring Cloud Gateway / Traefik / Envoy / Ingress Nginx)都可以替换为 Apache APISIX 的解决方案。</p> <p><strong>在 SOA 下有 Apache APISIX SLB 解决方案,在微服务架构下有 Apache APISIX Gateway,在 Kubernetes 部署有 Apache APISIX Ingress,在 Service Mesh 里部署有 Apache APISIX mesh</strong>。</p> <p><img src="https://static.apiseven.com/202108/1630399942885-85b779b3-f646-4770-8714-bf7f48764149.png" alt="Apache APISIX 全流量数据面" referrerpolicy="no-referrer"></p> <p>从业务请求的流量方面看,当客户端发起请求时会经过 LB,经过 Gateway,请求被分发到后端业务服务。红色的部分(LB / Gateway / Spring Cloud Gateway / K8s Ingress / Sidecar)都可以选择 Apache APISIX 作为解决方案。Apache APISIX 支持多语言开发插件,可以在 Java 体系下使用 Java 编写插件。</p> <p>Apache APISIX 是全流量的数据面,在 LB、Gateway、Ingress、sidecar 方面 Apache APISIX 都有相应的解决方案,他们是统一的解决方案,在可观测性方面也是统一的方案。当解决方案统一时,管理控制链也是很容易实现出来的。</p> <h2>Apache APISIX 的可观测性</h2> <p>简单了解完 Nginx 和 Apache APISIX 之后,现在有两个问题:Apache APISIX 在可观测性上可以做哪些事情?Apache APISIX 可观测性上的优势在哪里?</p> <h3>Apache APISIX 支持采集的数据类型</h3> <p>Apache APISIX 支持采集的数据类型:</p> <ol> <li>Tracing - 集成 SkyWalking</li> <li>Metrics - 集成 SkyWalking / Prometheus</li> <li>Logging - 集成 SkyWalking / 其他日志平台</li> </ol> <p>Apache APISIX 是一个网关类的产品,可以替代 Nginx 或者其他的网关;在可观测性上 Apache APISIX 可以集成多个 APM 或可观测性系统,如:Tracing 部分可以集成 SkyWalking,Metrics 指标上可以集成 SkyWalking 或 Prometheus,Logging 可以集成 SkyWalking 以及其他一些日志系统。</p> <h3>Apache APISIX 在可观测性上的优势</h3> <h4>高度扩展能力</h4> <p>为什么 Apache APISIX 拥有强大的扩展能力?因为 Apache APISIX 支持多语言编写插件,可以使用 Lua、Java、Golang 等编程语言编写插件。Apache APISIX 可以通过插件扩展自身的能力。上文提到的三种数据类型,都是通过插件机制来实现的。</p> <h4>灵活的配置能力</h4> <p>举三个例子来介绍一下 Apache APISIX 灵活的配置能力。</p> <p>第一个例子是 <strong>Apache APISIX 可以在运行时修改 logging 的配置</strong>,如:增加或修改日志字段。修改日志字段是一个比较常见的需求,比如:业务刚开始上线时,配置好了日志字段,系统运行一段时间后,需要修改或者增加几个日志字段。如果使用 Nginx,通过修改 nginx.conf 文件实现需求,reload 使配置生效。 <strong>Apache APISIX 只需要通过脚本把字段配置好,就会动态生效</strong>。</p> <p>第二个灵活配置能力的例子是 Prometheus 的使用。在 Apache APISIX 里,想要创建 / 删除某一个 metric 或者扩展 metrics labels,只需要在 Prometheus 插件里新增一个 metircs 或者填写相关信息,Apache APISIX 有 hot reload 机制可以直接生效,不需要重启。</p> <p>第三个灵活的配置能力体现在 Apache APISIX 的实现。Apache APISIX 把路由对象全部管理起来,在内存里有一套对象管理机制。在 Apache APISIX 里给某个 API 加插件,生效的级别可以细化到 API,每一个 API 都可以绑定插件,也可以从 API 上把插件去掉。 Apache APISIX 可以精细化控制到每一个服务里面每一个 API 的可观测性数据采集。换句话说,你可以只采集最关心的数据,而且这些配置都是动态生效的,可以随时调整。</p> <h4>活跃的社区</h4> <p>Apache APISIX 最重要的一个优势是有一个活跃的社区,一个活跃的社区可以让产品快速迭代、变得越来越完善,让大家的需求得到满足。</p> <p><img src="https://static.apiseven.com/202108/1630399942886-396ea7a7-8138-460e-bd3b-cac5106f87e8.png" alt="Apache APISIX 社区活跃度对比图" referrerpolicy="no-referrer"></p> <p>上图展示的是 Apache APISIX(绿色)、Kong(浅蓝)、mosn(黄色)、bfe (深蓝)贡献者增长曲线,Apache APISX 增长趋势最快,曲线最为陡峭。 Apache APISIX 社区活跃度在同类型项目里面是最活跃的。</p> <h2>结合 Apache SkyWalking,在可观测性上做进一步提升</h2> <p>Apache APISIX 与 Apache SkyWalking 结合可以做哪些提升?除了 SkyWalking tracing 插件,还可以将tracing、metrics、logging、events 汇聚到 SkyWalking,借助 SkyWalking 的聚合能力让数据实现联动。</p> <h3>SkyWalking Satellite</h3> <p>SkyWalking Satellite 由 Apache APISIX社区、Apache SkyWalking 社区、百度深度合作开发。</p> <p><img src="https://static.apiseven.com/202108/1630399942887-2cfef2f8-3467-465a-b203-c0015dbac3bb.png" alt="Apache APISIX SkyWalking Satellite" referrerpolicy="no-referrer"></p> <p>SkyWalking Satellite 按照上图步骤采集数据,SkyWalking Satellite 可以部署到更靠近产生数据的前端,以 sidecar 的形式存在。</p> <p>图中从上往下业务请求经过 Apache APISIX 代理到 Upstream,Satellite 在 Apache APISIX 的旁边,以 sidecar 的形式部署,收集 Apache APISIX 的 tracing、metrics、logging 这三种数据类型的数据,通过 GRPC 协议发送给 SkyWalking。</p> <p>最重要的一点是:<strong>在这种部署方式下,Apache APISIX 不需要做任何改动就可以直接将三种数据类型集成到 SkyWalking</strong>。</p> <h3>ALS 方案</h3> <p>ALS (Access Log Service)将经过 Apache APISIX 的访问日志发送出来,在普通的 access log 上增加特殊的字段,如:增加关键字段便于生成拓扑图,同时聚合出 metrics。</p> <p>ALS 方案最大的好处是可以直接通过 access log 方式分析和聚合出拓扑图、metrics 、logging 这三种类型的数据。 在使用 Prometheus 时,如果配置了 URI 级别的 metrics 指标的统计,会导致整个 metrics 急剧膨胀。因为 URI 级别的服务可能有几十个,每个 metrics 后面可能有许多 labels,这会降低网关性能,增加 metrics 获取难度。<strong>使用 ALS 方案,通过流的方式将数据发送给 SkyWalking,把计算的事情交给 SkyWalking,后续也方便查询</strong>,不会出现每隔几秒钟拉取一次非常庞大的数据的情况。</p> <h3>将 Events 整合到 SkyWalking</h3> <p>常用的 Events 包括:配置分发、集群变化、健康检查。</p> <p><strong>配置分发</strong>:在配置 API 分发时,可能会新增路由、修改路由、删除路由或增加插件。</p> <p><strong>集群变化</strong>:集群发生变化时,需要知道集群里的服务数。如:扩容时 IP 会发生变化,变化体现在网关收到消息的时候。每个过程都是一个事件,这些事件都需要被暴露出来。</p> <p><strong>健康检查</strong>:主动探测是否健康,例如:业务请求失败率突然变高,事件探测到业务服务不健康,此时可以快速定位到问题。</p> <h2>延伸阅读</h2> <h3>Apache APISIX 的扩展机制的实现原理及其对稳定性的影响</h3> <p>问题:Apache APISIX 的扩展机制是怎么实现的?扩展这个功能是否对 Apache APISIX 本身稳定性有影响?</p> <p>答:Apache APISIX 扩展机制得益于它的架构,可以在各个 phases (rewrite / access / header_filter / body_filter / preread_filter / log)增加业务逻辑。</p> <p>至于稳定性方面, Apahce APISIX 已经开源了近 50 个插件,每一个插件都会有端到端的测试,这些插件都是经过验证的、是稳定可用的。但是自定义插件要遵循一定的规范,虽然很简单,但是大家也不能太随意。自定义插件的稳定性保证,需要由业务方自己来保证。</p> <h3>如何确认规则已生效?</h3> <p>问:Nginx 的 nginx.conf 文件里面可能配置了很多规则,后面的规则可能被前面的规则拦截,不清楚后面的规则是否生效,Apache APISIX 是否有什么方法知道哪些规则已生效?</p> <p>答:Nginx 的 nginx.conf 文件配置越多,配置服务越复杂,这个文件越难以维护。但是在 Apache APISIX 里配置文件是固定的,Apache APISIX 官方提供的配置就是在大多数场景下的最优配置,其他路由的配置是通过 API 的方式配置进去的,路由配置都是在内存里面的。</p> <p>在管理方式上,可以通过多种组织方式管理你的路由,例如,可以通过 Dashboard 来管理。</p> <p>举例说明,比如有一个服务叫 ABC,在这个服务下面可会有各种路由定义,路由定义通过列表的方式进行查看,路由里有一个字段叫优先级,可以通过配置路由的优先级字段,让相似的路由规则按照优先级依次匹配。另外一种路由查看方式是在 dashboard 中给 API 打上标签,可以让路由的管理变得更加人性化,便于按照标签过滤查询路由列表。</p> <h2>关于作者</h2> <p>金卫,Apache APISIX PMC 和 Apache SkyWalking committer。</p> <p><img src="https://static.apiseven.com/202108/1630399942888-562e6231-f105-425a-b303-f4d8843653f3.png" alt="金卫个人介绍" referrerpolicy="no-referrer"></p>

Kong-To-APISIX 迁移工具

<p>Apache APISIX 是一个生产可用的开源七层全流量处理平台,可作为 API 网关处理业务入口流量,具有极高性能、超低延迟,官方支持 dashboard 以及超过五十种插件。如果你正在使用 Kong,对 APISIX 感兴趣又苦于难以上手,不妨试试我们刚开源的迁移工具 <a href="https://github.com/api7/kong-to-apisix">Kong-To-APISIX</a>,助你一键平滑迁移。</p> <h2>工具能力</h2> <p>Kong-To-APISIX 利用 Kong 和 APISIX 的声明式配置文件实现了配置数据的迁移,并根据两侧架构和功能的不同做出相应适配。目前我们支持了 Kong 一侧 Route、Service、Upstream、Target,Consumer 以及三个插件 Rate Limiting、Proxy Caching 以及 Key Authentication 的配置迁移,并以 Kong 的 <a href="https://docs.konghq.com/getting-started-guide/2.4.x/overview/">Getting Started Guide</a> 为例,完成了一个最小的 demo。</p> <h2>使用方法</h2> <ol> <li> <p>使用 Deck 导出 Kong 声明式配置文件,点击查看<a href="https://docs.konghq.com/deck/1.7.x/guides/backup-restore/">具体步骤</a></p> </li> <li> <p>下载仓库并运行迁移工具,迁移工具会生成声明式配置文件 <code>apisix.yaml</code> 待使用</p> <pre><code class="language-shell">git clone https://github.com/api7/kong-to-apisix cd kong-to-apisix make build ./bin/kong-to-apisix migrate --input kong.yaml --output apisix.yaml # migrate succeed </code></pre> </li> <li> <p>使用 <code>apisix.yaml</code> 配置 APISIX, 点击查看<a href="https://apisix.apache.org/docs/apisix/stand-alone">具体步骤</a>。</p> </li> </ol> <h2>Demo 测试</h2> <ol> <li> <p>确保 docker 正常运行,部署测试环境,使用 docker-compose 拉起 APISIX、Kong</p> <pre><code class="language-shell">git clone https://github.com/apache/apisix-docker cd kong-to-apisix ./tools/setup.sh </code></pre> </li> <li> <p>根据 Kong 的 Getting Started Guide,为 Kong 添加配置并进行测试:</p> <p>a. 通过 Service 和 Route 暴露服务,进行路由转发</p> <p>b. 设置 Rate Limiting 和 Proxy Caching 插件做限流缓存</p> <p>c. 设置 Key Authentication 插件做认证</p> <p>d. 通过 Upstream 和 Target 设置负载均衡</p> <pre><code class="language-shell">./examples/kong-example.sh </code></pre> </li> <li> <p>导出 Kong 的声明式配置文件到 <code>kong.yaml</code></p> <pre><code class="language-shell">go run ./cmd/dumpkong/main.go </code></pre> </li> <li> <p>运行迁移工具,导入 <code>kong.yaml</code> 并生成 APISIX 配置文件 <code>apisix.yaml</code> 至 docker volumes</p> <pre><code class="language-shell">export EXPORT_PATH=./repos/apisix-docker/example/apisix_conf go run ./cmd/kong-to-apisix/main.go </code></pre> </li> <li> <p>在 APISIX 一侧测试迁移过后的路由、负载均衡、插件等是否正常运行</p> <ol> <li> <p>测试 key auth 插件</p> <pre><code class="language-shell">curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9080/mock # output: 401 </code></pre> </li> <li> <p>测试 proxy cache 插件</p> <pre><code class="language-shell"># access for the first time curl -k -I -s -o /dev/null http://127.0.0.1:9080/mock -H "apikey: apikey" -H "Host: mockbin.org" # see if got cached curl -I -s -X GET http://127.0.0.1:9080/mock -H "apikey: apikey" -H "Host: mockbin.org" # output: # HTTP/1.1 200 OK # ... # Apisix-Cache-Status: HIT </code></pre> </li> <li> <p>测试 limit count 插件</p> <pre><code class="language-shell">for i in {1..5}; do curl -s -o /dev/null -X GET http://127.0.0.1:9080/mock -H "apikey: apikey" -H "Host: mockbin.org" done curl -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9080/mock -H "apikey: apikey" -H "Host: mockbin.org" # output: 429 </code></pre> </li> <li> <p>测试负载均衡</p> <pre><code class="language-shell"> httpbin_num=0 mockbin_num=0for i in {1..8}; do body=$(curl -k -i -s http://127.0.0.1:9080/mock -H "apikey: apikey" -H "Host: mockbin.org") if [[ $body == *"httpbin"* ]]; then httpbin_num=$((httpbin_num+1)) elif [[ $body == *"mockbin"* ]]; then mockbin_num=$((mockbin_num+1)) fi sleep 1.5done echo "httpbin number: "${httpbin_num}", mockbin number: "${mockbin_num} # output: # httpbin number: 6, mockbin number: 2 </code></pre> </li> </ol> </li> </ol> <h2>总结</h2> <p>迁移工具的后续开发计划已在 Kong-To-APISIX 的 GitHub 仓库的 Roadmap 中呈现,欢迎大家访问 Kong-To-APISIX 的 <a href="https://github.com/api7/kong-to-apisix">GitHub 仓库地址</a> ,测试与使用 Kong-To-APISIX。 欢迎任何对这个项目感兴趣的人一同来为这个项目作贡献!有任何问题都可以在仓库的 Issues 区讨论。</p>

插件编排在 Apache APISIX 中的应用与实践

<h2>什么是 Apache APISIX?</h2> <p>Apache APISIX 是一个生产可用的七层全流量处理平台,可作为 API 网关处理业务入口流量,具有极高性能、超低延迟的显著特性。它内置了 50 多种插件,覆盖身份验证、安全防护、流量控制、Serverless、可观测性等多个方面,可满足企业客户常见的使用场景。</p> <p>如下方架构图所示,Apache APISIX 分为数据面(左侧)与控制面(右侧)两部分:通过控制面下发配置到 ETCD,数据面借助丰富的插件处理内外流量。</p> <p><img src="https://static.apiseven.com/202108/1630656070351-bc49128b-e0ef-4c93-830c-2ca7931dacce.png" alt="Apache APISIX 架构图" referrerpolicy="no-referrer"></p> <p>Apache APISIX 暴露了一组接口,方便我们为 API 绑定插件。如果我们希望为 API 增加限速能力,只需为 API 绑定 <code>limit-req</code> 插件:</p> <pre><code class="language-shell">curl -X PUT http://127.0.0.1:9080/apisix/admin/routes/1 -d ' { "uri": "/get", "methods": ["GET"], "upstream": { "type": "roundrobin", "nodes": { "httpbin.org:80": 1 } }, "plugins": { "limit-req": { "rate": 1, "burst": 2, "rejected_code": 503, "key": "remote_addr" } } }' </code></pre> <p>调用成功后,当请求到达该 API 时将进行限速管控。该示例使用 <code>limit-req</code> 实现 API 限速(特定功能),若针对“根据某个插件的处理结果,决定后续的请求处理逻辑”这种场景化需求,该怎么做呢?当前,现有的插件机制无法满足这种需求,这时便引申出插件编排的能力来解决这个问题。</p> <h2>什么是插件编排?</h2> <p>插件编排是低代码的一种表现形式,它可以帮助企业降低使用成本、增加运维效率,是企业数字化转型过程中不可或缺的能力。借助低代码 API 网关 Apache APISIX 中插件编排能力,我们可以轻松地将 50+ 插件通过“拖拽”的方式进行组合编排,被编排的插件也能够共享上下文信息,最终实现场景化需求。</p> <p>扩展上述 API 限速的场景:请求使用 <code>key-auth</code> 插件进行身份认证,若认证通过,将由 <code>kafka-logger</code>插件接管并进行日志记录;若认证失败(插件返回 401 状态码),将使用 <code>limit-req</code> 插件进行限速。</p> <p>见如下操作视频:</p> <iframe height="350" width="100%" src="https://api7-website-1301662268.file.myqcloud.com/202107/%E6%8F%92%E4%BB%B6%E7%BC%96%E6%8E%92.mp4" frameborder="0" referrerpolicy="no-referrer"> </iframe> 该视频中,Web 界面列出了目前已有的插件与画板,我们可以将插件拖拽到画板上进行编排,并填写插件绑定的数据,然后便完成了整个流程。在整个过程中: <ol> <li>操作可视化:/我们除了可以使用界面可视化创建 API 之外,还可以通过编排能力直观、清晰地进行场景化设计;</li> <li>流程可复用:通过导入、导出画板的 JSON 数据,可以便捷地复用编排生成的工程数据;</li> <li>组合新“插件”:将每一个场景视作一个插件,通过使用条件元件组合不同的插件,来实现插件创造“插件”。</li> </ol> <h2>实现原理</h2> <p>那么 Apache APISIX 是如何与低代码能力结合的呢?这需要数据面 Apache APISIX 与控制面 Apache APISIX Dashboard 共同配合完成。整体流程如下:</p> <p><img src="https://static.apiseven.com/202108/1630657206662-02f0f699-8b9d-40f0-b36b-dbf700b16e02.png" alt="低代码能力实现原理" referrerpolicy="no-referrer"></p> <h3>Apache APISIX</h3> <p>在 Apache APISIX 中,我们在 Route 实体中新增了 <a href="https://github.com/apache/apisix/pull/1982">script 执行逻辑</a>,可用于接收 Dashboard 生成的 Lua 函数并执行,它支持调用已有插件以复用代码。另外,它也作用于 HTTP 请求的生命周期中的各个阶段,如 <code>access</code>、<code>header_filer</code>、<code>body_filter</code> 等,系统会在相应阶段自动执行 <code>script</code> 函数对应阶段代码,见如下 <code>script</code> 示例:</p> <pre><code class="language-json">{ "script": "local _M = {} \n function _M.access(api_ctx) \n ngx.log(ngx.INFO,\"hit access phase\") \n end \nreturn _M" } </code></pre> <h3>Apache APISIX Dashboard</h3> <p>在 Dashboard 中,它包含了 Web 与 ManagerAPI 共两个子组件:Web 用于提供可视化界面,方便我们配置 API 网关;ManagerAPI 用于提供 RESTful API,供 Web 或其它客户端调用以便操作配置中心(默认为 ETCD),进而间接地控制 Apache APISIX。</p> <p>为了生成合法、有效的 script 函数,ManagerAPI 选择了 DAG 有向无环图的数据结构进行底层设计,并自主研发了 <a href="https://github.com/api7/dag-to-lua"><code>dag-to-lua</code></a> 项目:它将根节点作为开始节点,根据判断条件决定下一个流转插件,这将有效避免逻辑死循环。如下为 DAG 数据结构的示意图:</p> <p><img src="https://static.apiseven.com/202108/1630658809809-af6b83e5-af52-4ad8-b9df-d406156f58ec.png" alt="DAG 数据结构示意图" referrerpolicy="no-referrer"></p> <p>对应到 ManagerAPI 接收的 <code>script</code> 参数上,示例如下:</p> <pre><code class="language-json">{ "conf": { "1-2-3": { "name": "plugin-a", "conf": { ... } }, "4-5-6": { "name": "plugin-b", "conf": { ... } }, "7-8-9": { "name": "plugin-c", "conf": { ... } } }, "rule": { "root": "1-2-3", // 起始节点 ID "1-2-3": [ [ "code == 200", "4-5-6" ], [ "", "7-8-9" ] ] } } </code></pre> <p>即客户端将最终编排后的数据转换为上述格式后,ManagerAPI 会借助 <code>dag-to-lua</code> 项目生成 Lua 函数,并交给 Apache APISIX 执行。</p> <p>在 Web 侧,经过挑选、对比与项目验证,我们选择了蚂蚁金服开源的 X6 图编辑引擎作为插件编排 Web 部分的底层框架,除了完善、清晰的文档外,一系列开箱即用的交互组件以及节点可定制化能力也是我们选择它的原因。</p> <p><img src="https://static.apiseven.com/202108/1630659104761-a9657939-9de1-4cb6-bd97-71fad26f923e.jpeg" alt="X6 图编辑引擎" referrerpolicy="no-referrer"></p> <p>在编排实现过程中,我们抽象出了通用元件与插件元件的概念:通用元件是指开始节点、结束节点与条件判断节点,插件元件则是每一个可用的 Apache APISIX 插件,通过将这些元件拖拽到画板中来完成插件编排的流程。如图所示:</p> <p><img src="https://static.apiseven.com/202108/1630659171895-53a07770-1d0a-4cea-951d-f2801e679481.png" alt="元件拖拽" referrerpolicy="no-referrer"></p> <p>在拖拽过程中,我们需要限制一系列的边界条件,这里有几个例子:</p> <p>当插件未配置时,系统将出现「存在未配置的元件」的错误提示,可以直观地看到哪个插件没有配置数据:</p> <p><img src="https://static.apiseven.com/202108/1630659235474-5056489d-92b0-4253-9a6e-5135a60a5d3d.png" alt="配置数据" referrerpolicy="no-referrer"></p> <p>当编辑某条 API 时,若该 API 已经绑定了插件数据,当使用插件编排模式时,系统在检测后将出现警告信息,只有用户明确确认希望使用编排模式时,系统才能继续进行。这可以有效避免 API 数据被误操作的情况。</p> <p><img src="https://static.apiseven.com/202108/1630659280585-2484bc8b-4396-4034-8399-de4cf639b81b.png" alt="编排模式" referrerpolicy="no-referrer"></p> <p>此外,还存在诸如开始元件只能有一个输出、条件判断元件只能有一个输入等情况。试想:如果系统不加限制地让用户操作,不合理的插件组合既无意义,又会产生无法预料的错误,因此不断丰富边界条件,也是在设计插件编排时需要着重考虑的问题。</p> <p>当我们完成编排后,将使用 X6 暴露的 API 生成流程图的 JSON 数据,然后转换为系统需要的 DAG 数据,最终生成 Lua 函数。</p> <h2>未来展望</h2> <p>通过拖拽的方式,可以使得使用人员更方便地组合插件来满足不同的场景,以提升 API 网关可扩展能力与运维体验。在实际使用过程中,存在如下可以继续优化的问题:</p> <ol> <li>目前元件的边界判断条件还不够丰富,通过继续完善这些条件,以减少不合理的编排组合;</li> <li>当前编排示例不多,提供更多的参考示例可方便开发者学习、供用户使用;</li> <li>当前 Apache APISIX 使用了插件定义的 code 进行状态返回(异常则返回状态码,请求终止),可以支持更多 HTTP Response 字段甚至修改插件定义来扩展插件编排能力,如下述插件定义:</li> </ol> <pre><code class="language-json">local _M = { version = 0.1, priority = 2500, type = 'auth', name = plugin_name, schema = schema, # 新增的 result 字段,可存储插件运行结果,并传递到下个插件。 result = { code = { type = "int" } } } </code></pre>

ApacheCon Asia 2021:Apache APISIX 技术议题一览

<h2>关于 ApacheCon Asia 2021</h2> <p>ApacheCon 是 Apache 软件基金会的官方全球系列会议。自 1998 年以来,ApacheCon 一直吸引着各个层次的参与者,在 350 多个 Apache 项目及其不同的社区中探索 “明日的技术”。</p> <p>ApacheCon Asia 是 ApacheCon 组委会针对亚太地区举办的 ApacheCon 在线会议,主要目标在于更好地服务亚太地区快速增长的 Apache 用户和贡献者。ApacheCon Asia 2021 将于今年 8 月 6-8 日在线举办。</p> <p><img src="https://static.apiseven.com/202108/1630068124281-49ab6cdd-9b9f-4ddf-a498-7dcb101a2569.png" alt="ApacheCon Asia 2021" referrerpolicy="no-referrer"></p> <p>近期 ApacheCon Asia 2021 团队正式公布了大会日程,Apache APISIX 社区积极参与本次年度开源盛会,共提报了 8 个 API/微服务技术相关的议题,内容丰富,欢迎关注。同时,ApacheCon Asia 2021 也为无法参加在线会议的各位开源爱好者提供了每个议题的回放和录播视频,详情请移步 <a href="https://space.bilibili.com/609014805">Apache Local Community 北京本地社区</a>。</p> <h2>关于 API / 微服务技术议题</h2> <p>API 是服务连接的基石,通过 API 我们可以将各种服务进行搭建,并提供给用户使用;随着应用的复杂度越来越高,单体应用逐渐被拆分为微服务,产品可以快速迭代的同时也带来安全、维护和可观测性方面的技术挑战。</p> <p><a href="http://apisix.apache.org/">Apache APISIX</a> 是 Apache 顶级项目,也是全球最活跃的开源网关项目。在这个专题中,大家不仅可以了解 Apache APISIX 设计理念,也会学习到 Apache APISIX 项目的最佳实践。</p> <h2>Apache APISIX 在移动云对象存储 EOS 的应用与实践</h2> <p><strong>议题简介</strong></p> <p>该演讲主题主要是讲述 Apache APISIX 在中国移动公有云对象存储 EOS 中的应用与实践经验分享。首先介绍了中国移动公有云建设规划及对象存储产品发展演进历程,然后阐述了我们为什么选择 Apache APISIX 作为负载均衡网关,并对 EOS 流量治理架构演进三个阶段进行进行了详细介绍。同时,我们还分享了基于 Apache APISIX 我们解决了哪些实际生产问题,做了哪些方案及开发工作,最后对我们未来的演进做了一些规划说明。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124325-8a39eba3-43e6-4716-9577-d4727c4f621e.png" alt="Yanshan Chen" referrerpolicy="no-referrer"></p> <p>陈焱山 — 毕业后一直从事分布式存储软件开发及架构方案设计工作,深度参与了移动云建设过程,重点完成了对象存储相关的主要技术方案选型及落地开发建设工作。同时,在分布式对象存储领域拥有丰富的实战经验,目前正在思考基于APISIX七层网关实现对象存储流量治理工作,实现架构进一步升级。</p> <p><strong>分享时间</strong></p> <p>2021-08-07 15:30 GMT+8</p> <h2>使用 Apache APISIX 实现限流限速</h2> <p><strong>议题简介</strong></p> <p>谈到限流限速,人们往往最先想到的是 Nginx。然而 Nginx 通过配置文件的方式实现,每次变更都需要 reload,这让运维工作极其繁杂。另一方面,限速的条件被限制在 Nginx 的变量范围内,使得 Nginx 难以实现业务上精细化的限流限速需求。</p> <p>本次分享将带来如何使用 Apache APISIX 来实现动态、精细化、分布式的限流限速,以及如何通过插件编排来实现更符合业务需求的限流限速。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124286-d6b2893d-bfee-48f2-a41c-3385f94e5c7a.png" alt="Junxu Chen" referrerpolicy="no-referrer"></p> <p>陈军旭 — 互联网老兵,曾任职于新浪、迅雷、360等知名互联网公司。</p> <p><strong>分享时间</strong></p> <p>2021-08-06 13:30 GMT+8</p> <h2>用混沌网测试 Apache APISIX 的恢复能力</h2> <p><strong>议题简介</strong></p> <p>Apache APISIX 是领先的 API 网关 OSS 之一。为了确保一切按计划进行,APISIX 使用了不同种类的测试,包括单元、e2e 和模糊测试。然而,我们仍然不确定,当一些不正常但不可避免的情况发生时,例如网络故障、IO 压力或 pod 故障,APISIX 会如何表现。</p> <p>因此,在这里我们使用 Chaos Mesh,一个基于 Kubernetes 的混沌工程平台,可以顺利地注入不同种类的混沌,并将其整合到我们的CI管道中。在这个讲座的最后,听众会了解到混沌工程会在哪些方面给 API 网关带来好处,以及如何将混沌网整合到你自己的测试管道中。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124311-50a0ca52-afab-461a-90e2-a87cdd13eb67.png" alt="Shuyang Wu" referrerpolicy="no-referrer"></p> <p>Shuyang Wu — Apache APISIX 和 Chaos Mesh 的提交者,他领导了Chaos Mesh 与 Apache APISIX CI 的整合工作。</p> <p><strong>分享时间</strong></p> <p>2021-08-06 14:50 GMT+8</p> <h2>使用 Apache APISIX 进行认证和授权</h2> <p><strong>议题简介</strong></p> <p>认证和授权是 API 网关中非常必要的功能。这样一来,位于网关后面的服务就可以得到保护,避免未经授权或恶意的访问、数据泄露和黑客攻击。Apache APISIX 是一个动态、实时、高性能的API网关。而且它提供了许多插件,包括像 key-auth、Open-ID、wolf-RBAC 等认证和授权。本提案介绍了如何使用 Apache APISIX 来进行认证和授权。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124291-9f24abaf-310e-4d15-9022-6148ce52aff7.png" alt="Xinxin Zhu" referrerpolicy="no-referrer"></p> <p>Xinxin Zhu — Apache APISIX 的提交者,有多年CDN工作经验,熟悉网关。</p> <p><strong>分享时间</strong></p> <p>2021-08-06 15:30 GMT+8</p> <h2>依托社区让 Apache APISIX 高速发展</h2> <p><strong>议题简介</strong></p> <p>在过去的一年里,Apahce APISIX 已经成为全世界最活跃的 API 网关项目,除了自身技术先进外,更得益于社区的高度活跃。截止目前,已经有来自世界各地 225 位贡献者参与贡献,并且还在保持高速增长。本次分享会介绍 APISIX 在践行“社区重于代码”过程的一些心得。作为一个理想主义创业者,又是如何与 Apache 文化结合,让创业公司也能高速发展。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124295-d8bb30c9-d8a2-4aeb-a503-c5fbb547cf55.png" alt="Yuansheng Wang" referrerpolicy="no-referrer"></p> <p>Yuansheng Wang — 开源爱好者 Apache APISIX创始人和PMC成员</p> <p><strong>分享时间</strong></p> <p>2021-08-06 16:10 GMT+8</p> <h2>如何将 Apache APISIX 扩展为一个服务网格的边车</h2> <p><strong>议题简介</strong></p> <p>在这个议题中将介绍 apisix-mesh-agent 项目,它有一些能力将 Apache APISIX 扩展为服务网格场景中的边车程序,更重要的是,它使用 xDS 协议从 Istio、Kuma 等控制平面获取配置。之后,我将介绍关于在服务网中使用 Apache APISIX 的未来计划和期望。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124301-91d8aa04-96a3-4485-9112-bd1341f23078.png" alt="Chao Zhang" referrerpolicy="no-referrer"></p> <p>Chao Zhang — Apache APISIX PMC,OpenResty贡献者,开源爱好者,现在我正在研究 Service Mesh、Kubernetes 和API Gateway。</p> <p><strong>分享时间</strong></p> <p>2021-08-07 13:30 GMT+8</p> <h2>Apache APISIX 的演变</h2> <p><strong>议题简介</strong></p> <p>Apache APISIX是最受欢迎的API网关之一:https://github.com/apache/apisix,我将介绍一下 Apache APISIX 的发展,包括:</p> <ol> <li>我们所做的好决定</li> <li>我们所做的不好的决定</li> <li>我们的未来计划</li> </ol> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124304-0d2eb7f5-2346-4be4-972b-7828f72b902e.png" alt="Zexuan Luo" referrerpolicy="no-referrer"></p> <p>Zexuan Luo — Apache APISIX 和OpenResty 的核心开发者</p> <p><strong>分享时间</strong></p> <p>2021-08-07 14:10 GMT+8</p> <h2>基于 Apache APISIX 的 KUBERNETES INGRESS 的实现</h2> <p><strong>议题简介</strong></p> <p>介绍基于 Apache APISIX 的 Kubernetes Ingress 的优势以及 Apache APISIX Ingress 的特点。</p> <p><strong>分享嘉宾</strong></p> <p><img src="https://static.apiseven.com/202108/1630068124297-bb14f11b-b2f4-46b9-b0c4-3e988581d759.png" alt="Wei Jin" referrerpolicy="no-referrer"></p> <p>Wei Jin — Apache APISIX PMC / Apache apisix-ingress-controller Founder / Apache Skywalking Committer</p> <p><strong>分享时间</strong></p> <p>2021-08-07 14:50 GMT+8</p>

API 网关 APISIX 在谷歌云 T2A 和 T2D 的性能测试

<h2>背景</h2> <p>2022 年 7 月 13 日 Google Cloud 发布了第一个基于 Arm® 架构的 Tau T2A 的 VM 系列预览版。T2A VM 由基于 Ampere® Altra® Arm 的处理器提供支持,谷歌宣称其拥有极具吸引力的价格和卓越的单线程性能。</p> <p>值得注意的是,Ampere® Altra® Arm 是一款云原生处理器,基于 Ampere® Altra® Arm 处理器的 Tau T2A 虚拟机也因此能以高效的方式运行横向扩展的云原生应用程序。</p> <p>那么具体实际体验和性能如何呢?我们以一个云原生的 API 网关为例,带大家一起看看 Google Cloud Tau T2A 虚拟机的表现。这里,我们选择了 Apache APISIX 在 Google Cloud Tau T2A 服务器环境上进行安装测试。</p> <p>Apache APISIX 是一个云原生、高性能、可扩展的 API 网关。基于 NGNIX + LuaJIT 和 etcd,APISIX 与传统 API 网关相比,具有动态路由和插件热加载特性,特别适合云原生架构下的 API 管理。</p> <p><img src="https://static.apiseven.com/2022/blog/0722/1.PNG" alt="network error/APISIX Architecture.png" referrerpolicy="no-referrer"></p> <h2>前期准备</h2> <p>首先需要在 Google Cloud 上启动一个 T2A 实例,操作系统选择 Ubuntu 20.04。</p> <p><img src="https://static.apiseven.com/2022/blog/0722/2.png" alt="network error/Google Cloud T2A.png" referrerpolicy="no-referrer"></p> <p>然后安装 Docker,方便后续使用容器化的方式来安装部署 Apache APISIX。</p> <pre><code class="language-shell">sudo apt-get update &amp;&amp; sudo apt-get install docker.io </code></pre> <h2>部署 Apache APISIX</h2> <p>Apache APISIX 使用 etcd 作为配置中心,所以这里需要先启动一个 etcd 实例。</p> <pre><code class="language-shell">sudo docker run -d --name etcd \ -p 2379:2379 \ -e ETCD_UNSUPPORTED_ARCH=arm64 \ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \ -e ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379 \ rancher/coreos-etcd:v3.4.16-arm64 </code></pre> <p>启动 Apache APISIX 实例:</p> <pre><code class="language-shell">sudo docker run --net=host -d apache/apisix:2.14.1-alpine </code></pre> <p>注册路由。</p> <pre><code class="language-shell">curl "http://127.0.0.1:9080/apisix/admin/routes/1" \ -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "uri": "/anything/*", "upstream": { "type": "roundrobin", "nodes": { "httpbin.org:80": 1 } } }' </code></pre> <p>访问测试。</p> <pre><code class="language-shell">curl -i http://127.0.0.1:9080/anything/das </code></pre> <pre><code class="language-shell">HTTP/1.1 200 OK ..... </code></pre> <h2>Google Cloud T2D vs Google Cloud T2A</h2> <p>从上述操作来看,Apache APISIX 在 Google Cloud Tau T2A 上的安装和兼容性测试都能顺利完成。那么 Google Cloud T2A 的实际性能到底如何呢?接下来我们将使用 Apache APISIX 分别在 Google Cloud T2A 和 Google Cloud T2D 上做性能测试对比,来看看其实际表现。</p> <p>Google Cloud T2D 是 Google Cloud Tau 系列的另一款机型,是基于 AMD x86 架构的,所以上述 etcd 安装步骤略有不同:</p> <pre><code class="language-shell">sudo docker run -d --name etcd \ -p 2379:2379 \ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \ -e ALLOW_NONE_AUTHENTICATION=yes \ -e ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379 \ bitnami/etcd:3.4.16 </code></pre> <p>为简单起见,本次测试 APISIX 中只启用了一个 Worker,以下性能测试数据均在单核 CPU 上运行。</p> <h3>场景一:单上游</h3> <p>该场景下将使用单个上游(不包含任何插件),主要测试 APISIX 在纯代理回源模式下的性能表现。在本地环境中进行测试:</p> <pre><code class="language-shell"># apisix: 1 worker + 1 upstream + no plugin # 注册路由 curl http://127.0.0.1:9080/apisix/admin/routes/1 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/hello", "plugins": { }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980":1 } } }' </code></pre> <h3>场景 2:单个上游 + 两个插件</h3> <p>该场景下将使用单个上游与两个插件进行,主要测试 APISIX 在开启 <code>limit-count</code> 和 <code>prometheus</code> 两个核心性能消耗插件时的表现。</p> <pre><code class="language-shell"># apisix: 1 worker + 1 upstream + 2 plugins (limit-count + prometheus) # 注册路由 curl http://127.0.0.1:9080/apisix/admin/routes/1 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/hello", "plugins": { "limit-count": { "count": 2000000000000, "time_window": 60, "rejected_code": 503, "key": "remote_addr" }, "prometheus": {} }, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980":1 } } }' </code></pre> <h3>数据比较</h3> <p>上述两个场景中,分别从请求 QPS(每秒查询数) 和延迟时间两个层面进行了相关的测试对比。结果如下:</p> <ul> <li>QPS 比较</li> </ul> <p><img src="https://static.apiseven.com/2022/blog/0722/3.png" alt="network error/QPS comparison.png" referrerpolicy="no-referrer"></p> <ul> <li>请求延迟比较</li> </ul> <p><img src="https://static.apiseven.com/2022/blog/0722/4.png" alt="network error/Latency comparison.png" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/2022/blog/0722/5.png" alt="network error/data comparison.png" referrerpolicy="no-referrer"></p> <p>从以上数据也可以看出,在 API Gateway 等网络 IO 密集计算场景下,T2A 相比同系列的 T2D 虚拟机,在性能上仍然有差距。不过另一个好消息是,在同等配置情况下,T2A 的价格要比 T2D 便宜 10% 左右。在实际机器选型时,用户可以根据自己的业务体量来灵活决策。</p> <h2>总结</h2> <p>本文主要使用 Apache APISIX 对比 Google Cloud T2A 和 Google Cloud T2D 的性能。可以看出,在 API 网关等网络 IO 密集计算场景中,Google Cloud T2A 相比 T2D,表现虽然不是那么亮眼,但是作为 Google Cloud 在 ARM 架构下的第一次尝试,相信其会在 ARM 架构的虚拟机上持续发力,也期待它的后续迭代表现。</p>

深度剖析 Apache APISIX Mesh Agent

<p>今年 6 月,支流科技推出了<a href="https://www.apiseven.com/blog/apisix-mesh-agent-release">基于 Apache APISIX 的服务网格方案</a>,其中 Apache APISIX 将作为服务网格的数据面,与支持 xDS 协议的控制面配合,进而托管服务间的流量。在该方案中有一个不可或缺的组件 <a href="https://github.com/api7/apisix-mesh-agent">apisix-mesh-agent</a>,它作为数据面与控制面的中间层,完成了很多适配性的工作,进而让 Apache APISIX 在接近零改造的情况下即可完美地工作在服务网格中。</p> <p>本文将对 apisix-mesh-agent 进行分析,介绍其使用定位及其实现的功能。</p> <h2>使用定位</h2> <p>从使用角度上讲,apisix-mesh-agent 将和 Apache APISIX 共同运行在同一个(sidecar)容器内,事实上 Apache APISIX 也是由 apisix-mesh-agent fork 得到,这有点类似于 Istio 中 pilot-agent 与 Envoy 之间的关系。另外,它也负责在 Pod 启动后注入 iptables 规则(拦截服务实例的出入流量)。</p> <p><img src="https://static.apiseven.com/202108/1630639686171-133e72d8-fcd1-4436-9be5-bc1d9b6ebb32.png" alt="apisix-mesh-agent" referrerpolicy="no-referrer"></p> <p>从图中可以看到,apisix-mesh-agent 负责和控制面交互(获取配置变更),Apache APISIX 负责实际的流量处理和转发。</p> <h2>获取配置变更</h2> <p>前面我们提到,apisix-mesh-agent 将和实现了 xDS 协议的控制面进行交互,从控制面处获取最新的配置变更,具体来说,apisix-mesh-agent 会通过 CDS (Cluster Disocvery Service)来获取集群列表,并进一步通过 EDS(Endpoint Discovery Service)获取这些集群的最新实例列表;集群列表和集群对应的实例列表将在 apisix-mesh-agent 内部转换为 Apache APISIX 的 <a href="https://apisix.apache.org/zh/docs/apisix/terminology/upstream/">Upstream</a> 对象(EDS 的数据则是转换为 Upstream 的节点信息)。</p> <p>此外,apisix-mesh-agent 通过 LDS(Listener Discovery Service)获取动态监听器,apisix-mesh-agent 获取监听器后,主要的目的是获取其中的 HTTP Connection Manager 内的 RDS 名称表,进而能够发起 RDS 请求(Route Discovery Service),获取到具体的路由配置,这部分路由配置将被映射到 Apache APISIX 的 <a href="https://apisix.apache.org/zh/docs/apisix/terminology/route/">Route</a> 对象。至于监听器本身,并不会为 Apache APISIX 所用。</p> <p>细心的读者可能会发现,这些监听器本身就是一个路由匹配条件,即目标服务的地址和端口信息,如果 Apache APISIX 忽略了这些监听器,那么是否会出现串路由的情况?事实上,我们通过在 Nginx 核心中添加了一个新的变量 $connection_original_dst,这个变量的值是流量原始的地址和端口信息(没有被 iptables 劫持的情况下),有了这一层判断以后,路由之间则不会出现乱串的情况。(感兴趣的同学可以<a href="https://github.com/api7/apisix-mesh-agent/blob/main/nginx/patches/nginx-1.19.3-connection-original-dst.patch">点此</a>了解这个变量的实现。)</p> <pre><code class="language-json"># 10.0.5.113:8000 监听器下的某路由配置 { "name": "vhost1", "domains": [ "apisix.apache.org" ], "routes": [ { "name": "route1", "match": { "path_specifier": { "prefix": "/foo/baz" } }, "action": { "route": { "cluster_specifier": { "cluster": "httpbin.default.svc.cluster.local" } } } } ] } # 转换成 Apache APISIX 的配置后: { "uris": [ "/foo/baz*" ], "hosts": [ "apisix.apache.org" ], # 该路由匹配时需要判断对应连接原始的目标地址是否是 "10.0.5.113:8000",即 # httpbin.default.svc.cluster.local 这一服务的 ClusterIP(只考虑 Kubernetes # 场景)。 "vars": [ ["connection_original_dst", "==", "10.0.5.113:8000"] ] # upstream_id 定义了 httpbin.default.svc.cluster.local 这一服务, # 包含其最新的实例地址和其他相关的负载均衡、健康检查等配置。 "upstream_id": "90ba12b92e2d417f6802536696431724d59856ea" } </code></pre> <p>转换后的配置将被缓存在 apisix-mesh-agent 的内存中。那么数据又该怎么传递给 Apache APISIX 呢?</p> <h2>模拟 ETCD</h2> <p>Apache APISIX 使用 ETCD 作为其配置中心,通过 ETCD 的 watch 机制不断获取最新的配置从而保证其处理正确性。为了能让 Apache APISIX 在不做任何改变的情况下就能工作于服务网格场景中,我们让 apisix-mesh-agent 模拟了 ETCD V3 APIs,从而能够将其从控制面获取到的配置(加以转换后)传递到本地的 Apache APISIX 进程。</p> <p>目前 apisix-mesh-agent 主要实现的 API 有 Range (获取指定配置)和 Watch (监听指定配置变更)两个只读的 ETCD API 接口,并且支持了根据键值来获取不同的资源(如路由和上游资源)。对于 Watch 接口,我们需要推送增量更新事件,而类似 Istio 这样的控制面使用的是 xDS 协议中的 SotW(State of the World)变种,即每次都是推送全量数据,为了能够获取到更新事件,apisix-mesh-agent 会将当前获取到的数据与上一次的数据状态进行对比,从而对比得到增量更新事件然后传递到 Apache APISIX。</p> <h2>总结</h2> <p>通过引入 apisix-mesh-agent 这一中间层,我们成功地将 Apache APISIX 部署在服务网格中,未来 apisix-mesh-agent 也将支持 xDS 协议中更多的功能以及可观测性和证书管理等方面的能力,从而让 APISIX Mesh 变得更加成熟与强大。</p>

Apache APISIX PMC 王院生:一线奋斗快二十年,希望我的代码有价值且长寿

<p>本文来自七牛云 ECUG 活动组对 Apache APISIX PMC 王院生的采访,现将文字摘录如下:</p> <p><strong>问:请您简单介绍一下自己和目前从事的工作</strong></p> <p><img src="https://static.apiseven.com/202108/1631256596586-671c5f27-1609-4029-98aa-8e7b879e545e.jpeg" alt="王院生" referrerpolicy="no-referrer"></p> <p><strong>王院生</strong>:大家好,我叫王院生,是 Apache APISIX 的 PMC 成员,也是深圳支流科技创始人兼 CTO。2015 年通过一本电子书结识了社区中的广大朋友,以此开始在开源圈子里交了很多朋友。</p> <p>作为理想主义创业程序员,希望为世界做点贡献。于是 2019 年创造了开源项目 APISIX 并把它捐献给 Apache,同年创办了深圳支流科技,召集了很多志同道合热爱开源的伙伴一起创业。</p> <p><strong>问:您自称是“理想主义青年”,请问您的“技术理想”是什么?</strong></p> <p><strong>王院生</strong>:我认为每个人都有理想主义情节,只是有些人害羞没有表达出来。作为程序员,我在一线奋斗快到二十年,我认为程序员的技术理想都差不多:希望我的代码可以被每个人用到。并且希望这些代码对社会、对世界是有价值并且长寿的。</p> <p><strong>问:目前已经有很多可选的网关产品,为什么还要进入这个行业进行摸索?</strong></p> <p><strong>王院生</strong>:企业用户真正落地网关产品时会发现适合的网关产品其实并不多。网关产品是企业的流量出入口,还兼顾着保护和隐藏内部服务的作用,随着微服务、云原生等应用的更多落地,对 API 网关提出了新的需求,不仅要满足稳定性、安全性、性能、动态等基本要求,还需要支持动态扩缩容、容器化部署等云原生新特性。</p> <p>新时代有新需求,而我和创始人刚好在这方面有技术积累,所以就选择了这个行业。</p> <p><strong>问:从诞生到成为 ASF 顶级项目只用了一年,您认为这中间最关键的因素是什么?</strong></p> <p><strong>王院生</strong>:这个问题经常被问到,Apache 的核心价值:社区大于代码。我和温铭从 2015 年开始组织社区,积累了大量社区用户,为 APISIX 快速毕业做了最好铺垫。通过下面贡献者增长曲线图可以看到,APISIX 从开源到现在一直是以成熟项目的速度在发展。</p> <p><img src="https://static.apiseven.com/202108/1631256596591-fc5e48c5-8a7a-4d31-a6db-81722d1d60f4.png" alt="Apache APISIX contributor growth" referrerpolicy="no-referrer"></p> <p><strong>问:云原生是当下的热门话题,但不同行业、领域的人对云原生的理解不同。您会如何定义或者理解云原生?</strong></p> <p><strong>王院生</strong>:看过很多这类定义,我给个比较简单的版本:支持容器化部署、支持弹性扩缩容。</p> <p><strong>问:请就云原生相关生态在国内的发展趋势发表一些您的观点和看法吧。</strong></p> <p><strong>王院生</strong>:云原生相关生态在国内目前还是比较早期的阶段,基础设施这块整体来说国内还是追赶阶段。</p> <p><strong>问:谈云原生离不开基础设施。您认为在云原生生态中的基础设施有什么特征?最关键的是什么?网关在当中是在承担一个什么角色?</strong></p> <p><strong>王院生</strong>:既然是基础设施,那么最典型的特征就是:具有强通用性,不绑定任何具体业务或软硬件平台。API 网关其实是把一些安全、审计、服务治理等一些通用功能内置实现,让应用只关心应用。</p> <p><strong>问:有观点认为“Kubernetes 已经成为云时代的操作系统”;有人认为云原生的未来主要看 K8s,Service Mesh 和 Serverless 的发展,最终将从资源云化到业务云化。您怎么看?</strong></p> <p><strong>王院生</strong>:如果给未来十年后主流的后端架构投票,我会投给 Service Mesh。这个方向我是认可的。只不过它目前在易用、配套设施、效能等方面还没完全准备好,这需要时间,无法一蹴而就。</p> <p><strong>问:您认为一线开发者,应该如何看待云原生?</strong></p> <p><strong>王院生</strong>:云原生是一套在云端构建和运行软件应用的方法,可以归结为一套技术方法论。作为一线开发者应尽早进入云原生世界,从中学习优秀的技术架构和基础设计,掌握方法论为公司和个人选择最适合的技术发展路线。</p>

张晋涛提名成为 Apache APISIX committer 同时提名成为中国首位 K8s ingress-nginx reviewer

<p>近日,来自支流科技的工程师张晋涛通过投票提名成为 Apache APISIX committer,与此同时,张晋涛也被添加为 Kubernetes ingress-nginx 项目的 reviewer,这也是首位来自中国的 Kubernetes Ingress Controller reviewer。</p> <p>以下是 OSCHINA 对张晋涛的专访:</p> <p><strong>问:请简单介绍下自己吧?</strong></p> <p><strong>张晋涛</strong>:我是张晋涛,是一个热爱开源,喜欢折腾的工程师。目前是 Apache APISIX committer, Kubernetes ingress-nginx reviewer 。在 Docker 和 Kubernetes 相关的很多项目都贡献过代码。我也是个一岁孩子的父亲。</p> <p><img src="https://static.apiseven.com/202108/1631245191267-54671967-3bdf-4327-9bb1-09220023bfdc.png" alt="张晋涛" referrerpolicy="no-referrer"></p> <p><strong>问:您是如何成为 Apache APISIX committer 的?成为 Apache APISIX committer 之后,有什么新的工作规划吗?</strong></p> <p><strong>张晋涛</strong>:我维护了 Apache APISIX 的 Helm chart 以及为 Apache APISIX Ingress Controller 增加了 consumer 和 annotation 等特性,同时还有一些社区的活动。在完成项目 GA 后,经过社区的投票和公示流程后,正式成为 Apache APISIX committer 。</p> <p>最主要的规划还是在 Apache APISIX Ingress controller 上,准备对其架构进行调整,让它更加好用。</p> <p><strong>问:您是什么时候加入支流科技的?为什么加入,支流科技的哪些地方吸引您?目前主要负责什么工作?</strong></p> <p><strong>张晋涛</strong>:我是今年 4 月底加入支流科技的。支流科技是一家开源商业化公司,团队中的所有工程师都深度参与开源项目,在公司中也是以技术作为主导。全员远程,通过 GitHub 和 Slack 等进行协作,这种方式跟我平时参与开源项目体验一致。而且这里可以更好的发挥我的特长,也可以给我足够的自由,所以我比较喜欢。</p> <p>我在这边主要负责 Apache APISIX Ingress Controller ,我希望打造一款更棒的 Ingress Controller 。</p> <p><strong>问:您成为 Kubernetes ingress-nginx 项目的 reviewer,可以给大家介绍下 reviewer 需要做些什么吗?项目选择 reviewer 的流程是怎样的?一般注重考察什么?</strong></p> <p><strong>张晋涛</strong>:reviewer 除了对项目进行代码贡献外,最主要就是去 review 其他贡献者的代码了,以保证项目的代码质量和正确性。与此同时,也需要去跑测试,来验证 PR 是否符合预期等。</p> <p>至于项目选择 reviewer 的流程,这个大前提是对项目要有持续的贡献,以及非常熟悉该项目。此外还要求至少是 5 个 PR 的主要 reviewer,以及合并过至少 20 个 实质性的 PR 。通过项目的 approver 进行提名,且没有其他人反对,就可以通过 PR 把名字写在项目的 OWNERS 文件中了。Kubernetes 相关项目的 reviewer 详细职责和选择流程等可参考此<a href="https://github.com/kubernetes/community/blob/master/community-membership.md#reviewer">文档</a></p> <p>刚才也提到了,能否把握项目代码的质量和正确性这个是基础,也是大前提。在此基础上会重点考察贡献度和对项目的熟悉程度。</p> <p><strong>问:您也是目前 Kubernetes Ingress Controller 开源项目唯一一位来自中国的 reviewer。可以聊聊 Kubernetes Ingress Controller 在 Kubernetes 生态中的地位和影响吗?有没有什么“过来人”的建议可以给到国内开发者?</strong></p> <p><strong>张晋涛</strong>:Kubernetes Ingress Controller 是 Kubernetes 社区的 Ingress Controller 实现,也是当前使用最为广泛的 Ingress Controller 。很多公司或者产品都在使用它作为 Kubernetes 集群的流量入口。目前在 GitHub 上有 10.5K 的 star 。</p> <p>Kubernetes Ingress Controller 这个项目在各种生产环境下久经考验,代码质量和功能都是很不错的推荐大家可以关注和学习下。我在此项目中也看到过很多来自国内的开发者,说明大家对此项目也都是有所关注的,希望大家能够持续贡献。</p> <p><strong>问:您运营「K8S 生态周报」已经两年多了,有没有总结出一些规律,比如大家更关注什么样的信息?</strong></p> <p><strong>张晋涛</strong>:自我 2019 年开始运营「K8S 生态周报」的这两年多的时间里,发现大家更关注 Kubernetes 自身的一些特性变更或者漏洞之类的,这也是我在 「K8S 生态周报」的每一篇中都有一个部分介绍“上游进展”的原因。此外,大家对于一些新项目,或者新的提案也比较感兴趣,会给我留言,或者加我微信来讨论。</p> <p><strong>问:在「K8S 生态周报」内容的选取上有什么偏好或者原则吗?</strong></p> <p><strong>张晋涛</strong>:在每篇「K8S 生态周报」的开头,我都有写:「K8S 生态周报」内容主要包含我所接触到的 Kubernetes 生态相关的每周值得推荐的一些信息。</p> <p>我在《K8S 生态周报一周年了》这篇文章中也曾介绍我最初的想法。“Kubernetes 生态中相关信息和变化有很多,在这个信息爆炸的时代,稍不留神就会错过很多有价值的信息,但持续的去追这些消息,也过于浪费时间,而且还需要去筛选信息。”所以我维护的 「K8S 生态周报」并非简单的信息收集,还包含着我的思考及评价。每次周报的内容,都是我个人认为值得推荐和关注的信息。尤其是这是一份技术型的周报,而非纯资讯型,这也是和其他人或组织维护的周报最大的不同。</p> <p><strong>问:您在许多技术社区都开设了专栏,也经常更新文章,写文章给您带来了哪些收获?您是如何在写代码之余还保持热情和精力大量产出文章的?</strong></p> <p><strong>张晋涛</strong>:写文章对我而言也是一个总结和学习的过程。一方面,写文章的时候,对知识进行总结归纳,可以让我加深印象以及梳理清楚其中的逻辑。另一方面,毕竟写文章和记笔记不同,会公开出来,所以对其中可能之前含糊的点,在写文章的时候,也会再次考证和实践。同时,因为我写的文章,也结识了不少朋友。</p> <p>写文章确实需要花费不少的时间和精力,我个人认为主要还是得做好时间得规划和管理。这方面我也还在探索和学习,做的并不好。目前可能更多的还是选择压榨其他时间吧。我一般会选择早上起来写文章。我使用 GitHub 的 Project 和 Issue 等来管理自己需要做的事情。</p> <p><strong>问:看您之前的采访是 2014 年从 Docker “入坑”容器技术领域,近几年非常关注 Kubernetes,能对二者的发展做简单预测吗?对 Kubernetes 弃用 Docker 怎么看?</strong></p> <p><strong>张晋涛</strong>:其实 Docker 和 Kubernetes 我都在持续的关注。我在 Docker 主仓库的贡献者排行榜中是第 66 位。</p> <p>Docker 目前仍然是使用最为广泛的容器运行时和桌面容器化开发者工具。而且 Docker 目前的定位,也确实在做开发者工具方面投入了很多精力,包括默认集成了漏洞扫描工具,更好的磁盘管理工具等。未来的三五年内,Docker 在这个方面应该仍然是开发者的第一选择。</p> <p>Kubernetes 是云原生的基石,未来的三五年内,仍然会是最主要的技术方向。此外,Kubernetes 也正在扩展其应用场景,包括 IoT 等领域。</p> <p>在 Kubernetes 宣布 kubelet 中废弃对 dockershim 维护后,我曾专门写过一篇文章 《Kubernetes 弃用 Docker 了?Docker 不能用了?别逗了!》 事实上,这件事情影响并没那么大。因为不用在 Kubernetes 代码仓库中的 dockershim 组件,也可以使用外部的 dockershim 组件。另外,从 Docker 中捐赠出来的 containerd 项目,已经是 CNCF 毕业项目了,我认为迁移到 containerd 也是个不错的选择。开发者本地的环境,可以继续使用 Docker 作为开发工具。</p> <p><strong>问:此外您也是 Containerd、Docker、Helm、Kubernetes、KIND 等众多开源项目的 contributor,可以分享下参与开源项目贡献和开源社区活动的经验和想法吗?</strong></p> <p><strong>张晋涛</strong>:我认为参与开源贡献和开源社区活动等,主要还是应该从兴趣出发。在参与项目的时候,应该尽可能选择自己接触较多的,或者更感兴趣的项目。不要为了参与而参与,这样会把自己搞的比较累,而且对自己和社区都不一定是好事儿。</p> <p>开源社区是开放和包容的,无论你是提交 PR 来改进项目,还是提 issue 反馈 bug ,社区都是欢迎的。</p> <p>另外需要补充的一点,也是之前很多人在问我的一个问题,“参与开源社区有什么回报?”我参与开源很久了,除了目前我在支流科技可以全职做开源外,之前待过的任何一家公司,我都只能利用自己的业余时间去参与,花费了挺多时间和精力。但其实并没有任何物质上的回报。可能只是兴趣所在吧,恰好现在把兴趣变成了工作(还有一份不错的薪水)。欢迎任何对开源感兴趣的小伙伴给我发邮件交流,zhangjintao@moelove.info</p> <p><strong>问:云原生向来也是开源争议比较多的领域之一,比如在开源上贡献较多的公司和另一些从开源项目中获取价值较多的公有云厂商在利益上的冲突。您怎么看?这种矛盾可以解决吗?</strong></p> <p><strong>张晋涛</strong>:这个问题由来已久,但也不是不能调和。就目前我看到的情况来说,相比 3 年多之前已经好很多了,多数云厂商也正在积极贡献。无论说是为了争取拿到更多话语权,还是为了解决自己所遇到的问题,在回馈社区。我觉得都是好事儿。只有这样持续的进行正循环,开源软件和社区才能更好的发展。</p>

差之毫厘:etcd 3 完美支持 HTTP 访问?

<p>etcd 升级到 3.x 版本后,其对外 API 的协议从普通的 HTTP1 切换到了 gRPC。为了兼顾那些不能使用 gRPC 的特殊群体,etcd 通过 gRPC-gateway 的方式代理 HTTP1 请求,以 gRPC 形式去访问新的 gRPC API。(由于 HTTP1 念起来太过拗口,以下将之简化成 HTTP,正好和 gRPC 能够对应。请不要纠结 gRPC 也是 HTTP 请求的这种问题。)</p> <p>Apache APISIX 开始用 etcd 的时候,用的是 etcd v2 的 API。从 Apache APISIX 2.0 版本起,我们把依赖的 etcd 版本升级到 3.x。由于 Lua 生态圈里面没有 gRPC 库,所以 etcd 对 HTTP 的兼容帮了我们很大的忙,这样就不用花很大心思去补这个短板了。</p> <p>从去年 10 月发布 Apache APISIX 2.0 版本以来,现在已经过去了 8 个月。在实践过程中,我们也发现了 etcd 的 HTTP API 的一些跟 gRPC API 交互的问题。事实上,拥有 gRPC-gateway 并不意味着能够完美支持 HTTP 访问,这里还是有些细微的差别。</p> <h2>打破 gRPC 的默认限制</h2> <p>就在几天前,etcd 发布了 v3.5.0 版本。这个版本的发布,了却困扰我们很长时间的一个问题。</p> <p>跟 HTTP 不同的是,gRPC 默认限制了一次请求可以读取的数据大小。这个限制叫做 “MaxCallRecvMsgSize”,默认是 4MiB。当 Apache APISIX 全量同步 etcd 数据时,假如配置够多,就会触发这一上限,报错 “grpc: received message larger than max”。</p> <p>神奇的是,如果你用 etcdctl 去访问,这时候却不会有任何问题。这是因为这个限制是可以在跟 gRPC server 建立连接时动态设置的,etcdctl 给这个限制设置了一个很大的整数,相当于去掉了这一限制。</p> <p>由于不少用户碰到过同样的问题,我们曾经讨论过对策。</p> <p>一个想法是用增量同步模拟全量同步,这么做有两个弊端:</p> <ol> <li>实现起来复杂,要改不少代码</li> <li>会延长同步所需的时间</li> </ol> <p>另一个想法是修改 etcd。既然能够在 etcdctl 里面去除限制,为什么不对 gRPC-gateway 一视同仁呢?同样的改动可以作用在 gRPC-gateway 上。</p> <p>我们采用了第二种方案,给 etcd 提了个 <a href="https://github.com/etcd-io/etcd/pull/13077">PR</a></p> <p><img src="https://static.apiseven.com/202108/1639465584634-26435c89-3e1c-4fb9-b094-057fce0f769d.png" alt="Github etcd" referrerpolicy="no-referrer"></p> <p>最新发布的 v3.5.0 版本就包含了我们贡献的这个改动。如果你遇到 “grpc: received message larger than max”,不妨试一下这个版本。这一改动也被 etcd 开发者 backport 到 3.4 分支上了。3.4 分支的下一个发布,也会带上这个改动。</p> <p>这件事也说明 gRPC-gateway 并非百试百灵。即使用了它,也不能保证 HTTP 访问能够跟 gRPC 访问有一样的体验。</p> <h2>对服务端证书的有趣用法</h2> <p>Apache APISIX 增加了对 etcd mTLS 的支持后,有用户反馈一直没法完成校验,而用 etcdctl 访问则是成功的。在跟用户交流后,我决定拿他的证书来复现下。</p> <p>在复现过程中,我注意到 etcd 日志里面有这样的报错:</p> <pre><code class="language-text">2021-06-09 11:10:13.022735 I | embed: rejected connection from "127.0.0.1:50898" (error "tls: failed to verify client's certificate: x509: certificate specifies an incompatible key usage", ServerName "") WARNING: 2021/06/09 11:10:13 grpc: addrConn.createTransport failed to connect to {127.0.0.1:12379 0 }. Err :connection error: desc = "transport: authentication handshake failed: remote error: tls: bad certificate". Reconnecting... </code></pre> <p>“bad certificate” 错误信息,初看像是因为我们发给 etcd 的客户端证书不对。但仔细瞧瞧,会发现这个报错是在 gRPC server 里面报的。</p> <p>gRPC-gateway 在 etcd 里面起到一个代理的作用,把外面的 HTTP 请求变成 gRPC server 能处理的 gRPC 请求。</p> <p>大体架构如下:</p> <pre><code class="language-text">etcdctl ----&gt; gRPC server Apache APISIX ---&gt; gRPC-gateway ---&gt; gRPC server </code></pre> <p>为什么 etcdctl 直连 gRPC server 能通,而中间加一层 gRPC-gateway 就不行?</p> <p>原来当 etcd 启用了客户端证书校验之后,用 gRPC-gateway 连接 gRPC server 就需要提供一个客户端证书。猜猜这个证书从哪来?</p> <p>etcd 把配置的服务端证书直接作为这里的客户端证书用了。</p> <p>一个证书既在服务端上提供验证,又在客户端上表明身份,看上去也没什么问题。除非……</p> <p>除非证书上启用了 server auth 的拓展,但是没有启用 client auth。</p> <p>对有问题的证书执行<code>openssl x509 -text -noout -in /tmp/bad.crt</code></p> <p>会看到这样的输出:</p> <pre><code class="language-text">X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication </code></pre> <p>注意这里的 “TLS Web Server Authentication”,如果我们把它改成 “TLS Web Server Authentication, TLS Web Client Authentication”,抑或不加这个拓展,就没有问题了。</p> <p>etcd 上也有关于这个问题的 <a href="https://github.com/etcd-io/etcd/issues/9785">issue</a></p> <p><img src="https://static.apiseven.com/202108/1639465662863-30bc4fa9-8b7c-47d9-a73e-810bd690a588.png" alt="etcd issue" referrerpolicy="no-referrer"></p> <h2>结语</h2> <p>虽然我们在上文列出了几点小问题,但是瑕不掩瑜,etcd 对 HTTP 访问的支持还是一个非常有用的特性。</p> <p>感谢 Apache APISIX 的用户们,正是因为我们有着广阔的用户群,才能发现 etcd 的这些细节上的问题。我们作为 etcd 的一大用户,在之后的日子里也将一如既往地跟 etcd 的开发者多多交流。</p>

Apache APISIX × Rancher: 极速部署更好用的开源网关和 Ingress Controller

<h2>Rancher 介绍</h2> <p>Rancher 是一个开源的企业级多集群 Kubernetes 管理平台,实现了 Kubernetes 集群在混合云+本地数据中心的集中部署与管理,以确保集群的安全性,加速企业数字化转型。</p> <h2>Apache APISIX 介绍</h2> <p>Apache APISIX 是一款开源的高性能、动态云原生网关,由深圳支流科技有限公司于 2019 年捐赠给 Apache 基金会,当前已经成为 Apache 基金会的顶级开源项目,也是 GitHub 上最活跃的开源网关项目。Apache APISIX 当前已经覆盖了 API 网关,LB,Kubernetes Ingress,Service Mesh 等多种场景。</p> <h2>前置条件</h2> <ul> <li>已将现有 Kubernetes 集群纳入 Rancher 管理。</li> </ul> <h2>部署 Apache APISIX 和 Apache APISIX Ingress controller</h2> <p>在 Rancher 的 <em>Tools - Catalogs</em> 页面可以应用商店(catalogs)的配置,我们在这里<a href="https://github.com/apache/apisix-helm-chart">添加 Apache APISIX 的 Helm 仓库</a>。</p> <p><img src="https://static.apiseven.com/202108/1630113667126-2ddff7a7-4f0f-4596-bbf9-fef381c1134b.png" alt="Rancher catalogs" referrerpolicy="no-referrer"></p> <p>保存完成后,即可选择 <em>Apps</em> 页面进行 Apache APISIX 的部署了。选择 <em>Launch</em> 便可看到 Apache APISIX 的仓库信息了。这里我们直接选择 apisix 即可。</p> <p><img src="https://static.apiseven.com/202108/1630113736719-fe1e7065-f4e3-4921-b0ab-781a85d1f7e7.png" alt="Rancher apps" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/1630113793613-e1a7b6af-4d6e-4435-88c7-f7a116dec11b.png" alt="Rancher APISIX" referrerpolicy="no-referrer"></p> <p>接下来着只需要在此页面中进行简单的配置即可。 <strong>因为我们想要同时部署 APISIX Ingress controller,所以在底部的 Answers 中填入 <code>ingress-controller.enabled=true</code> 这个配置项</strong> 。保存即可完成部署。</p> <p><img src="https://static.apiseven.com/202108/1630113851464-0698e370-3073-4af4-8400-b9c2e81e5bde.png" alt="Rancher APISIX config" referrerpolicy="no-referrer"></p> <p>稍等片刻即可完成部署。</p> <p><img src="https://static.apiseven.com/202108/1630113893968-c54842fd-354e-4210-b7bc-2a139f6e03b4.png" alt="Rancher APISIX config" referrerpolicy="no-referrer"></p> <h2>部署示例项目</h2> <p>我们使用 <code>kennethreitz/httpbin</code> 作为示例项目进行演示。这里也直接在 Rancher 中完成部署。</p> <p><img src="https://static.apiseven.com/202108/1630113957198-fa73dee2-52b8-4840-9bbc-b1bbc2c620eb.png" alt="Rancher APISIX demo" referrerpolicy="no-referrer"></p> <h2>使用 Apache APISIX 作为网关代理</h2> <p>我们先演示如何使用 Apache APISIX 作为网关代理 Kubernetes 集群中的服务。</p> <pre><code class="language-shell">root@apisix:~$ kubectl -n apisix exec -it `kubectl -n apisix get pods -l app.kubernetes.io/name=apisix -o name` -- bash bash-5.1# curl httpbin.default/get { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.default", "User-Agent": "curl/7.76.1" }, "origin": "10.244.3.3", "url": "http://httpbin.default/get" } </code></pre> <p>可以看到在 Apache APISIX 的 Pod 内可正常访问示例项目。接下来使用 Apache APISIX 对该示例项目进行代理。</p> <p>这里我们使用 <code>curl</code> 调用 Apache APISIX 的 admin 接口,创建一条路由。将所有 host 头为 <code>httpbin.org</code> 的请求转发给 <code>httpbin.default:80</code> 这个实际的应用服务上。</p> <pre><code class="language-shell">bash-5.1# curl "http://127.0.0.1:9180/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d ' { "uri": "/*", "host": "httpbin.org", "upstream": { "type": "roundrobin", "nodes": { "httpbin.default:80": 1 } } }' {"action":"set","node":{"value":{"uri":"\/*","create_time":1623834078,"update_time":1623834078,"priority":0,"upstream":{"type":"roundrobin","hash_on":"vars","pass_host":"pass","nodes":{"httpbin.default:80":1},"scheme":"http"},"id":"1","status":1,"host":"httpbin.org"},"key":"\/apisix\/routes\/1"}} </code></pre> <p>你会得到类似上面的输出,接下来验证是否代理成功:</p> <pre><code class="language-shell">bash-5.1# curl http://127.0.0.1:9080/get -H "HOST: httpbin.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.76.1", "X-Forwarded-Host": "httpbin.org" }, "origin": "127.0.0.1", "url": "http://httpbin.org/get" } </code></pre> <p>得到上面的输出,说明已经通过 Apache APISIX 代理了示例项目的流量。接下来我们试试在集群外通过 Apache APISIX 访问示例项目。</p> <pre><code class="language-shell">root@apisix:~$ kubectl -n apisix get svc -l app.kubernetes.io/name=apisix NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE apisix-admin ClusterIP 10.96.142.88 &lt;none&gt; 9180/TCP 51m apisix-gateway NodePort 10.96.158.192 &lt;none&gt; 80:32763/TCP 51m </code></pre> <p>在使用 Helm chart 部署的时候,默认会将 Apache APISIX 的端口通过 NodePort 的形式暴露出去。我们使用 Node IP + NodePort 的端口进行访问测试。</p> <pre><code class="language-shell">root@apisix:~$ curl http://172.18.0.2:32763/get -H "HOST: httpbin.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.58.0", "X-Forwarded-Host": "httpbin.org" }, "origin": "10.244.3.1", "url": "http://httpbin.org/get" } </code></pre> <p>可以看到,<strong>在集群外已经可以通过 Apache APISIX 作为网关代理 Kubernetes 集群内的服务了</strong>。</p> <h2>使用 APISIX Ingress controller 代理服务</h2> <p>我们可以直接在 Rancher 中添加 Ingress ,Apache APISIX Ingress controller 会自动将路由规则同步至 Apache APISIX 中,完成服务的代理。</p> <p><img src="https://static.apiseven.com/202108/1630114006945-ae48d3d2-b706-4353-b5d9-f372727faade.png" alt="Rancher APISIX Ingress" referrerpolicy="no-referrer"></p> <p>注意右下角, 我们添加了 <code>kubernetes.io/ingress.class: apisix</code> 的 annotation 配置,用于支持集群内多 ingress-controller 的场景。</p> <p>保存后,可看到如下界面:</p> <p><img src="https://static.apiseven.com/202108/1630114069907-3de3d9d0-be9b-4b78-ad9f-1553480057be.png" alt="Rancher APISIX Ingress" referrerpolicy="no-referrer"></p> <p>在终端下测试是否代理成功:</p> <pre><code class="language-shell">root@apisix:~$ curl http://172.18.0.2:32763/get -H "HOST: httpbin-ing.org" { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin-ing.org", "User-Agent": "curl/7.58.0", "X-Forwarded-Host": "httpbin-ing.org" }, "origin": "10.244.3.1", "url": "http://httpbin-ing.org/get" } </code></pre> <p>可以看到也正常代理了。</p> <p>除了以上方式外,Apache APISIX Ingress controller 通过 CRD 的方式对 Kubernetes 进行了扩展,你也可以通过发布 <code>ApisixRoute</code> 等这种自定义资源来完成 Kubernetes 中服务的对外暴露。</p> <h2>总结</h2> <p>你可以在 Rancher 中使用 Apache APISIX 的官方 Helm 仓库直接部署 Apache APISIX 和 APISIX Ingress controller 。并且 Apache APISIX 可通过作为网关,或者 APISIX Ingress controller 的数据面来承载业务流量。</p> <h2>未来展望</h2> <p>Apache APISIX 已经与 Rancher 社区达成合作,未来你可以直接在 Rancher 自带的应用仓库中找到 Apache APISIX ,不再需要手动添加 Helm 仓库了。</p>

使用 Java 编写 Apache APISIX 插件

<p>Apache APISIX 支持多语言编写插件了!不会 Lua 也没关系,现在可以用你熟悉的语言编写插件,文末还有<strong>视频教程</strong>。</p> <h2>简介</h2> <h3>为什么 Apache APISIX 要支持多语言编写插件</h3> <p>在支持多语言编程插件前,Apache APISIX 只支持使用 Lua 语言编写插件,需要开发者掌握 Lua 和 OpenResty 相关的开发能力。然而相对于主流开发语言 Java 和 Go 来说,Lua 和 OpenResty 属于相对小众的技术,开发者很少。如果从头开始学习 Lua 和 OpenResty,需要付出相当多的时间和精力。</p> <p>开发团队在进行技术选型的时候,最重要的考量就是所选技术是否与本团队技术栈相匹配,然而小众的技术栈就限制了 Apache APISIX 在更广阔的场景下进行技术落地。</p> <p>现在 Apache APISIX 支持多语言开发插件,<strong>更重要的是支持语言所在的开发生态圈,使用者可以使用自己熟悉的技术栈来开发 Apache APISIX</strong>。以支持 Java 为例,使用者不仅可以使用 Java 语言编写插件,还可以融入 Spring Cloud 生态圈,广泛使用生态圈内的各种技术组件。</p> <h3>Apache APISIX 多语言支持架构图</h3> <p><img src="https://static.apiseven.com/202108/1630114449786-b39fbf27-e739-48da-ada3-1f90ea3bbd7d.png" alt="APISIX arch" referrerpolicy="no-referrer"></p> <p>上图左边是 Apache APISIX 的工作流程,右边的 plugin runner 是指插件运行器,泛指多语言支持的项目。本文档下面提到的 apisix-java-plugin-runner 项目就是支持 Java 语言的 plugin runner。</p> <p>当你在 Apache APISIX 中配置一个 plugin runner 时,Apache APISIX 会启动一个子进程运行 plugin runner,该子进程与 Apache APISIX 进程属于同一个用户。当我们重启或重新加载 Apache APISIX 时,plugin runner 也将被重启。</p> <p>如果你为一个给定的路由配置了 ext-plugin-* 插件,命中该路由的请求将触发 Apache APISIX,通过 unix socket 向 plugin runner 执行 RPC 调用。调用细分为两个阶段:</p> <ul> <li>ext-plugin-pre-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之前</li> <li>ext-plugin-post-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之后</li> </ul> <p>根据需要配置 plugin runner 的执行时机。</p> <p>plugin runner 会处理 RPC 调用,在其内部创建一个模拟请求,然后运行多语言编写的插件,并将结果返回给 Apache APISIX。</p> <p>多语言插件的执行顺序是在 ext-plugin-* 插件配置项中定义的。像其他插件一样,它们可以被启用并在运行中重新定义。</p> <h2>搭建开发环境</h2> <p>首先需要搭建 Apache APISIX 的运行环境或者开发环境,参考 <a href="https://apisix.apache.org/zh/docs/apisix/how-to-build/">构建 Apache APISIX</a>,以及 Java 项目的开发环境,参考 <a href="https://apisix.apache.org/zh/docs/java-plugin-runner/development/">构建 apisix-java-plugin-runner</a>。</p> <p><strong>注意</strong>:Apache APISIX 和 apisix-java-plugin-runner 需要位于同一实例上。</p> <h2>进入调试模式</h2> <h3>设置 Apache APISIX 进入调试模式</h3> <p>这里是指让 Apache APISIX 以调试的方式运行外部插件,在 <code>config.yaml</code> 中增加以下配置</p> <pre><code class="language-text">ext-plugin: path_for_test: /tmp/runner.sock </code></pre> <p>这个配置的意思是,Apache APISIX 相当于 client 端,会监听位于 <code>/tmp/runner.sock</code> 位置上的 Unxi Domain Socket 链接。</p> <h3>设置 apisix-java-plugin-runner 进入调试模式</h3> <p>在启动 <code>Main class(org.apache.apisix.plugin.runner.PluginRunnerApplication)</code>之前,需要配置用户级的环境变量 <code>APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock</code> 和 <code>APISIX_CONF_EXPIRE_TIME=3600</code>。</p> <p>如果你是使用 IDEA 进行开发,那么配置好的环境变量示意如下:</p> <p><img src="https://static.apiseven.com/202108/1630114494391-c7573084-87a0-4806-849f-1f8384392612.png" alt="IDEA Environment Variables" referrerpolicy="no-referrer"></p> <p>apisix-java-plugin-runner 相当于 server 端,在启动时会主动创建 <code>/tmp/runner.sock</code> 文件,并在这个文件上与 Apache APISIX 进行 Unix Domain Socket 通信。</p> <h2>开发指南</h2> <h3>场景</h3> <p>我们以一个场景来代入开发过程:需要验证请求 header 中 token 的有效性,验证 token 的方式是用请求中携带 token 作为参数,访问 SSO 固定的接口,如果 token 验证通过则放行请求,验证失败则阻止请求。</p> <h3>配置 Apache APISIX</h3> <p>先给插件命名为 <code>TokenValidator</code>,然后设计属性,为了尽可能做到动态配置,属性设计如下</p> <pre><code class="language-text">{ "validate_header": "token", "validate_url": "https://www.sso.foo.com/token/validate", "rejected_code": "403" } </code></pre> <p>启动 Apache APISIX,然后新增一条路由配置,指定该路由需要调用 apisix-java-plugin-runner 的 <code>TokenValidator</code> 插件,示例如下</p> <pre><code class="language-text">curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/get", "plugins":{ "ext-plugin-pre-req":{ "conf":[ { "name":"TokenValidator", "value":"{\"validate_header\":\"token\",\"validate_url\":\"https://www.sso.foo.com/token/validate\",\"rejected_code\":\"403\"}" } ] } }, "upstream":{ "nodes":{ "httpbin.org:80":1 }, "type":"roundrobin" } } </code></pre> <p>需要注意的是,<code>TokenValidator</code> 的属性需要经过 json 转义,作为 string 类型进行配置。</p> <p>(这里上游地址配置为 httpbin.org,方便调试)</p> <h3>开发 Java 插件</h3> <p>在 runner-plugin/src/main/java/org/apache/apisix/plugin/runner/filter 目录下,新增 <code>TokenValidatr.java</code>,代码初始骨架如下</p> <pre><code class="language-Java">package org.apache.apisix.plugin.runner.filter; import org.apache.apisix.plugin.runner.HttpRequest; import org.apache.apisix.plugin.runner.HttpResponse; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Component public class TokenValidator implements PluginFilter { @Override public String name() { return "TokenValidator"; } @Override public Mono&lt;Void&gt; filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) { return chain.filter(request, response); } } </code></pre> <p>需要继承 <code>PluginFilter</code>接口,重写 <code>name</code> 和 <code>filter</code>函数。</p> <p>重写 <code>name</code> 的返回值,和前面配置 APISIX 的路由属性中 "name" 保持一致。</p> <p>完整代码如下:</p> <pre><code class="language-Java">package org.apache.apisix.plugin.runner.filter; import com.google.gson.Gson; import org.apache.apisix.plugin.runner.HttpRequest; import org.apache.apisix.plugin.runner.HttpResponse; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; @Component public class TokenValidator implements PluginFilter { @Override public String name() { return "TokenValidator"; } @Override public Mono&lt;Void&gt; filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) { // parse `conf` to json String configStr = request.getConfig(this); Gson gson = new Gson(); Map&lt;String, Object&gt; conf = new HashMap&lt;&gt;(); conf = gson.fromJson(configStr, conf.getClass()); // get configuration parameters String token = request.getHeader((String) conf.get("validate_header")); String validate_url = (String) conf.get("validate_url"); boolean flag = validate(token, validate_url); // token verification results if (!flag) { String rejected_code = (String) conf.get("rejected_code"); response.setStatusCode(Integer.parseInt(rejected_code)); return chain.filter(request, response); } return chain.filter(request, response); } private Boolean validate(String token, String validate_url) { //TODO: improve the validation process return true; } } </code></pre> <h3>测试</h3> <p>在 Apache APISIX 上配置的上游服务是 httpbin.org,可以访问 Apache APISIX,触发路由,让 Apache APISIX 调用 apisix-java-plugin-runner 去执行 TokenValidator 插件,测试一下 Java 插件效果。</p> <pre><code class="language-text">curl -H 'token: 123456' 127.0.0.1:9080/get { "args": {}, "headers": { "Accept": "/", "Host": "127.0.0.1", "Token": "123456", "User-Agent": "curl/7.71.1", "X-Amzn-Trace-Id": "Root=1-60cb0424-02b5bf804cfeab5252392f96", "X-Forwarded-Host": "127.0.0.1" }, "origin": "127.0.0.1", "url": "http://127.0.0.1/get" } </code></pre> <h2>部署</h2> <p>插件开发完成后,部署操作可以参考 <a href="https://apisix.apache.org/zh/docs/java-plugin-runner/how-it-works/">部署 apisix-java-plugin-runner</a>。</p> <h2>视频教程</h2> <iframe height="350" width="600" src="https://api7-website-1301662268.file.myqcloud.com/2021-06-21-use-Java-to-write-Apache-APISIX-plugins.mp4" frameborder="0" referrerpolicy="no-referrer"> </iframe>

Chaos Mesh 助力 Apache APISIX 提升稳定性

<p>Apache APISIX 是 Apache 基金会下的顶级项目,目前在生产环境中已经通过每日几百亿次请求量的考验。随着社区的发展,Apache APISIX 的功能越来越多,需要与外部组件产生的交互也越来越多,随之而来的不确定性呈指数级增长。在社区中,我们也收到了用户反馈的一些问题,这里举两个例子。</p> <h2>问题场景</h2> <h3>场景一</h3> <p>在 Apache APISIX 的配置中心, etcd 与 Apache APISIX 之间出现意外的高网络延迟时,Apache APISIX 能否仍然正常运行进行流量过滤转发?</p> <h3>场景二</h3> <p>用户在 issue 反馈,当 etcd 集群中的一个节点失效而集群仍然可以正常运行时,会出现与 Apache APISIX admin API 交互报错的情况。</p> <p>尽管 Apache APISIX 在 CI 中通过单元 / e2e / fuzz 测试覆盖了大部分情景,然而尚未覆盖到与外部组件的交互。当发生网络波动、硬盘故障、或是进程被杀掉等难以预料的异常行为时,Apache APISIX 能否给出合适的错误信息、是否可以保持或自行恢复到正常的运行状态呢?为了测试覆盖到用户提出的场景,以及在投入生产环境前主动发现类似的问题,经过社区讨论决定使用 PingCAP 开源的混沌工程平台 Chaos Mesh 进行测试。</p> <p>混沌工程是一种在系统基础设施上进行试验,主动找出系统中的脆弱环节的方法,从而确保系统具有抵御生产环境中失控环境的能力。混沌工程最早由 Netflix 提出,用以模拟从而抵御早期云服务的不稳定性。随着技术的演进,现在的混沌工程平台提供了更多种类的故障可供注入,依靠 Kubernetes 也可以更方便地控制故障半径。这些都是 Apache APISIX 选择 Chaos Mesh 的重要原因,但作为开源社区,Apache APISIX 深知只有活跃的社区才能确保软件稳定使用和快速迭代,而这也是 Chaos Mesh 更加吸引人的特点。</p> <h2>如何在 APISIX 上应用混沌工程</h2> <p>混沌工程在单纯的注入故障以外,逐渐形成了一套完整的方法论。根据 Principle of Chaos Engineering 的推荐,部署混沌工程实验需要五个步骤:</p> <ol> <li>定义稳态,即找到一个证明正常运行的可量化指标。</li> <li>做出假设,假设指标在实验组和对照组都始终保持稳定状态。</li> <li>设计实验,引入运行中可能出现的故障。</li> <li>验证假设,即通过比较实验组和对照组的结果证伪假设。</li> <li>修复问题。</li> </ol> <p>接下来以上述两个用户反馈场景为例,依照这五个步骤为大家介绍 Apache APISIX 应用混沌工程的流程。</p> <h3>场景一</h3> <p><img src="https://static.apiseven.com/202108/1630115370517-7863c3ec-0e9d-4486-a4e8-dc17017bc6a7.png" alt="Chaos Mesh in APISIX" referrerpolicy="no-referrer"></p> <p>用一幅图来描述这个场景。对照上面的五个步骤,首先需要找到衡量 Apache APISIX 正常运行的可量化指标。在测试时最主要的方法是利用 Grafana 对 Apache APISIX 运行指标进行监测,找到可衡量的指标后,在 CI 中就可以从 Prometheus 中单独提取数据进行比较判断,这里使用了路由转发的 Request per Second(RPS)和 etcd 的可连接性 作为评价指标。另一点就是需要对日志进行分析,对于 Apache APISIX 就是查看 Nginx 的 error.log 判断是否有报错以及报错是否符合预期。</p> <p>在对照组也就是引入 Chaos 前进行实验,检测 set/get route 均能成功,etcd 可连接,并记录此时的 RPS。之后,使用 network chaos 添加 5s 的网络延迟 ,再次进行实验,此时 set route 失败,get route 成功,etcd 无法连接,RPS 与之前相比无明显变化。实验符合预期。</p> <h3>场景二</h3> <p><img src="https://static.apiseven.com/202108/1630115436031-885e3edc-c03b-4c68-beed-d9e170cf78d8.png" alt="Chaos Mesh in APISIX" referrerpolicy="no-referrer"></p> <p>进行同样的对照组实验之后引入 pod-kill chaos,复现了预期的错误。在随机删除集群中少数 etcd 节点的情况下,etcd 可连接性表现出时有时无,日志则打印出了大量连接拒绝的报错。更加有趣的是,在删除 etcd 端点列表的第一个或第三个节点时,设置路由正常返回,而只有在删除 etcd 端点列表中的第二个节点时,设置路由会报错 “connection refused”。</p> <p>排查发现原因在于 Apache APISIX 使用的 etcd lua API 选择端点时并不是随机而是顺序选择,因此新建 etcd client 进行的操作就相当于只绑定在一个 etcd 端点上导致持续性的失败。修复这个问题之后,还为 etcd lua API 添加了健康检查,确保不会在断开连接的 etcd 上进行大量的重复;以及增加了 etcd 集群完全断开连接时的回退检查,避免大量报错。</p> <h2>未来计划</h2> <h3>1. 借助 e2e 模拟场景进行混沌测试</h3> <p>目前在 Apache APISIX 中,仍然主要依靠人来识别系统中可能的脆弱点进行测试修复。对于开源社区来说,与之前提到的 Netflix 在企业中应用混沌工程不同,尽管在 CI 中测试,无需担心混沌工程的故障半径对生产环境的影响,但同时也无法覆盖生产环境中的复杂而全面的场景。</p> <p>为了覆盖更多的场景,未来社区计划利用现有的 e2e 测试模拟更加完整的场景,进行更大范围、更强随机性的混沌测试。</p> <h3>2. 为更多 Apache APISIX 项目添加混沌测试</h3> <p>除了为 Apache APISIX 找到更多可能的脆弱点之外,社区还计划为 Apache APISIX Dashboard 和 Apache APISIX Ingress Controller 等更多项目添加混沌测试。</p> <h3>3. 为 Chaos Mesh 添加功能</h3> <p>在部署 Chaos Mesh 时遇见一些暂不支持的功能,包括网络延迟的目标不支持选择 service,网络混沌无法指定容器端口注入等,Apache APISIX 社区未来也会协助 Chaos Mesh 添加相关功能。希望开源社区都会越来越好。</p>

Apache APISIX 和 Envoy 性能大比拼

<p>在 CNCF 组织的一场技术分享会上,第一次听到了 Envoy 这么一个东西,分享的嘉宾巴拉巴拉讲了一大堆,啥都没记住,就记住了一个特别新颖的概念“通信总线”,后面 google 了下 Envoy 这个东西到底是什么,发现官网上如是描述:</p> <p>“Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线”</p> <p>也就是说, Envoy 是为了解决 Server Mesh 领域而诞生一款 L7 代理软件,这里我网上找了一张图,我理解的 Envoy 大概是如下的部署架构。(如果错了请大佬指教)</p> <p><img src="https://static.apiseven.com/202108/20210617001.png" alt="图片" referrerpolicy="no-referrer"></p> <p>既然是 L7 的代理软件嘛,作为常年混迹 OpenResty 社区的老司机,自然忍不住把它拿来搞一搞,对比对比。</p> <p>我们选择的比试对象是 Apache APISIX,它是基于 OpenResty 实现的 API 网关。(其实也就是 L7 代理然后加了路由、认证,限流、动态上游等等之类的功能)</p> <p>为什么选择它呢,因为有一次社区分享的时候听说这货的路由实现非常棒,正好我们的现在业务的路由系统乱七八糟,扒拉了下 APISIX 的源码,发现确实是 6 到飞起,吊打我看到过的同类产品, 所以印象深刻,就它了!</p> <p>这里附上一张在 APISIX 官网扒拉的图,真是一图胜千言,一看就知道这玩意儿是怎么工作的。</p> <p><img src="https://static.apiseven.com/202108/20210617002.png" alt="架构图" referrerpolicy="no-referrer"></p> <p>开搞吧,首先我们去官网找到两个产品的最版本:</p> <p>Apache APISIX 1.5 和 Envoy 1.14</p> <p>(笔者在写这篇文章时的最新版)</p> <h4>构建环境准备</h4> <ul> <li>压力测试客户端:wrk;</li> <li>测试主要指标包括:网关延迟、QPS 和是否线性扩展;</li> <li>测试环境:微软云 Linux (ubuntu 18.04), Standard D13 v2 (8 vcpus, 56 GiB memory);</li> <li>测试方式 1:采用单核运行横向对比(因为它们都是基于 epoll 的 IO 模型,所以用单核压测验证它们的处理能力);</li> <li>测试方式 2:采用多核运行横向对比,主要是为了验证两者在添加多(进程|线程)的场景下其整体处理能力是否能够线性增长;</li> </ul> <h4>测试场景</h4> <p>这里我们用 NGINX 搭建了一个上游服务器,配置 2 个 worker,接收到请求直接应答 4k 内容,参考配置如下:</p> <pre><code class="language-text">server { listen 1980; access_log off; location = /hello { echo_duplicate 400 "1234567890"; } } </code></pre> <ul> <li>网络架构示意图如下:(绿色正常负载,未跑满。红色为高压负载,要把进程资源跑满,主要是 CPU)</li> </ul> <p><img src="https://static.apiseven.com/202108/20210617003.png" alt="图片" referrerpolicy="no-referrer"></p> <h4>路由配置</h4> <p>首先我们找到 APISIX 的入门配置指南,我们添加一条到 /hello 的路由,配置如下:</p> <pre><code class="language-text">curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '{、 "uri": "/hello", "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } }}' </code></pre> <p>需要注意的是,这里没并没有开始 proxy_cache 和 proxy_mirror 插件,因为 Enovy 并没有类似的功能;</p> <p>然后我们参考 Envoy 官方压测指导 为 Envoy 添加一条路由:</p> <pre><code class="language-text">static_resources: listeners: - name: listener_0 address: socket_address: { address: "0.0.0.0", port_value: 10000 } filter_chains: - filters: - name: envoy.http_connection_manager config: generate_request_id: false, stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/hello" } route: { cluster: service_test } http_filters: - name: envoy.router config: dynamic_stats: false clusters: - name: service_test connect_timeout: 0.25s type: LOGICAL_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN hosts: [{ socket_address: { address: "127.0.0.1", port_value: 1980 }}] circuit_breakers: thresholds: - priority: DEFAULT max_connections: 1000000000 max_pending_requests: 1000000000 max_requests: 1000000000 max_retries: 1000000000 - priority: HIGH max_connections: 1000000000 max_pending_requests: 1000000000 max_requests: 1000000000 max_retries: 1000000000 </code></pre> <p>上面的 generate<em>request_id、dynamic_stats 和 circuit_breakers 部分,在 Envoy 内部是默认开启,但本次压测用不到,需要显式关闭或设置超大阈值从而提升性能。(谁能给我解释下为什么这玩意儿配置这么复杂 -</em>-!)</p> <h4>压测结果</h4> <p>单条路由,不开启任何插件。开启不同 CPU 数量,进行满载压力测试。说明:对于 NGINX 叫 worker 数量,Envoy 是 concurrent ,为了统一后面都叫 worker 数量。</p> <table> <thead> <tr> <th style="text-align:left"><strong>进程数</strong></th> <th style="text-align:left"><strong>APISIX QPS</strong></th> <th style="text-align:left"><strong>APISIX Latency</strong></th> <th style="text-align:left"><strong>Envoy QPS</strong></th> <th style="text-align:left"><strong>Envoy Latency</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:left"><strong>1 worker</strong></td> <td style="text-align:left">18608.4</td> <td style="text-align:left">0.96</td> <td style="text-align:left">15625.56</td> <td style="text-align:left">1.02</td> </tr> <tr> <td style="text-align:left"><strong>2 workers</strong></td> <td style="text-align:left">34975.8</td> <td style="text-align:left">1.01</td> <td style="text-align:left">29058.135</td> <td style="text-align:left">1.09</td> </tr> <tr> <td style="text-align:left"><strong>3 workers</strong></td> <td style="text-align:left">52334.8</td> <td style="text-align:left">1.02</td> <td style="text-align:left">42561.125</td> <td style="text-align:left">1.12</td> </tr> </tbody> </table> <p>注:原始数据公开在 gist 预览。(https://gist.github.com/aifeiasdf/9fc4585f6404e3a0a70c568c2a14b9c9)</p> <p><img src="https://static.apiseven.com/202108/20210617004.png" alt="图片" referrerpolicy="no-referrer"></p> <p>QPS:每秒钟完成的请求数,数量越多越好,数值越大代表单位时间内可以完成的请求数量越多。从 QPS 结果看,APISIX 性能是 Envoy 的 120% 左右,核心数越多 QPS 差距越大。</p> <p>Latency:每请求的延迟时间,数值越小越好。它代表每请求从发出后需要经过多长时间可以接收到应答。对于反向代理场景,该数值越小,对请求的影响也就最小。从结果上看,Envoy 的每请求延迟要比 APISIX 多 6-10% ,核心数量越多延迟越大。</p> <p>可以看到两者在单工作线程|进程的模式下,QPS 和 Latency 两个指标差距不大,但是随着工作线程|进程的增加他们的差距逐渐放大,这里我分析可能有以下两方面的原因,NGINX 在高并发场景下用多 worker 和系统的 IO 模型进行交互是不是会更有优势,另外一方面,也可能是 NGINX 自身在实现上面对内存和 CPU 的使用比较“抠门”,这样累积起来的性能优势,以后详细评估评估。</p> <h4>总结</h4> <p>总体来说 APISIX 在响应延迟和 QPS 层面都略优于 Envoy, 由于 NGINX 的多 worker 的协作方式在高并发场景下更有优势,得益于此,APISIX 在开启多个 worker 进程后性能提升较 Enovy 更为明显;但是两者并不冲突, Envoy 的总线设计使它在处理东西向流量上有独特的优势, APISIX 在性能和延迟上的表现使它在处理南北向流量上具有海量的吞吐能力,根据自己的业务场景来选择合理的组件配合插件构建自己的服务才是正解。</p>

GigaOm 发布 API 网关评测报告:API7 和 Kong 企业版本性能对比

<p><img src="https://static.apiseven.com/202108/da52e6a2b6674d7b812ba1a21b5dd516~tplv-k3u1fbpfcp-watermark.image" alt="图片" referrerpolicy="no-referrer"></p> <p>近期,GigaOm 发布了一份 API 网关的性能评测报告,报告主要介绍了在不同压测场景下, API7(基于 Apache 顶级项目 APISIX 的企业版本) 和 Kong EE(Kong 企业版本) 二者的性能差异,<strong>结果显示 API7 性能明显优于 Kong EE,最高有数百倍的差距。</strong></p> <p>我们从完整的报告中摘录了测试场景和数据:</p> <ol> <li>1 万 rps 的压测,1 条路由,不启用插件</li> <li>1 万 rps 的压测,1 条路由,启用 JWT 插件</li> <li>1 万 rps 的压测,1000 条路由</li> </ol> <p>下面图表中,横坐标表示请求的分布百分比,纵坐标表示延迟的毫秒数。所以,延迟越低越好,越稳定越好。这表示网关可以稳定、高效的处理终端请求。</p> <p>下图是在不启用任何插件的情况下,APISIX 和 Kong 企业版的延迟对比。对于 95% 请求而言差异很小,但在 95% 之后延迟差异随后呈指数级增长,<strong>在达到 99.99 % 时,Kong EE 的延迟是 API7 的 30 多倍。</strong></p> <p><img src="https://static.apiseven.com/202108/GigaOm-1.png" alt="GigaOm-1" referrerpolicy="no-referrer"></p> <p text-align="center" font-size="8px">横坐标表示请求的分布百分比,纵坐标表示延迟的毫秒数,数值越小说明性能越好。</p> <p>在启用了 JWT 插件后,API7 和 Kong EE 的性能差距进一步拉大:Kong EE 的最大延迟到了 3778 毫秒,处于不可用状态,而同时 <strong>API7 的最大延迟仅有 14 毫秒,两者是数百倍的差距。</strong></p> <p><img src="https://static.apiseven.com/202108/GigaOm-2.png" alt="GigaOm-2" referrerpolicy="no-referrer"></p> <p text-align="center" font-size="8px">横坐标表示请求的分布百分比,纵坐标表示延迟的毫秒数,数值越小说明性能越好。</p> 上面两个场景都只有 1 条路由,在用户的实际生产环境,一般会有几百上千条路由。所以,下面的测试覆盖了 1000 条路由的情况。**在这个场景下,Kong EE 和 API7 也是接近 100 倍的差距。** <p><img src="https://static.apiseven.com/202108/GigaOm-3.png" alt="GigaOm-3" referrerpolicy="no-referrer"></p> <p text-align="center" font-size="8px">横坐标表示请求的分布百分比,纵坐标表示延迟的毫秒数,数值越小说明性能越好。</p> <p>API7 在各种测试场景下,都保持了低延迟和稳定,这对于企业用户尤为关键。</p> <h2>API7</h2> <p>Apache APISIX 是新一代的云原生 API 网关,提供丰富的流量管理功能,如负载均衡、动态上游、灰度发布、服务熔断、身份验证、可观测性等。</p> <p>API7 是深圳支流科技基于 Apache APISIX 实现的商业产品,除了包括上面提到基础功能外,还针对企业用户实现了多集群管理、多工作分区、权限管理、版本管理、审计、统计等功能。</p>

基于 Apache APISIX 实现的服务网格项目正式开源

<p>基于 Apache APISIX 开发的服务网格项目正式开源!欢迎大家<a href="https://github.com/api7/apisix-mesh-agent">下载使用</a>。</p> <h2>什么是服务网格</h2> <p>服务网格(Service Mesh),作为服务间通信的中间层,将诸如服务发现、负载均衡、熔断、限流、重试等的基础功能,下沉到一个轻量级的边车(Sidecar)组件,使得应用程序开发者可以更聚焦于应用本身的开发,不需要关注这类基础功能,从而提升开发效率。</p> <h2>什么时候需要服务网格</h2> <h3>案例一</h3> <p>A 公司微服务的技术栈涉及 Go、Java 和 C++ 等不同语言,同时为每种开发语言设计了基础框架,以完成服务治理的功能,然而每次框架的更新都需要覆盖各语言的版本以及联系所有业务开发团队进行更新。并且不同语言的框架由不同团队维护,导致沟通成本高昂且容易出现不兼容情况。</p> <p>因而 A 公司的架构师决定引入服务网格方案,将所有服务治理的能力下沉到网格,基础功能迭代不再与业务开发耦合,同时业务开发不再依赖开发框架,业务本身更加简洁。</p> <h3>案例二</h3> <p>B 公司现有的服务治理框架功能落后,年久失修,因业务发展及合规性等原因,现在需要实现协议转换和双向认证,技术负责人调研以后决定引入服务网格方案,借助网格的能力实现这些功能,同时摆脱现有服务治理框架的历史债,使得所有应用变得更加稳定和轻量。</p> <h2>使用 Apache APISIX 搭建服务网格</h2> <p><img src="https://static.apiseven.com/202108/129695198-60458d56-946e-4e38-887b-3d81c90eceab.png" alt="Apache APISIX mesh agent arch" referrerpolicy="no-referrer"></p> <p>鉴于 Apache APISIX 的优秀设计,我们不仅可以将 Apache APISIX 用于南北向流量管理,也可以用它管理服务网格的东西向流量,我们通过引入 APISIX-Mesh-Agent 这一组件,配合 Apache APISIX 进行使用,从而使得 Apache APISIX 可以对接业内广泛使用的服务网格控制面,例如: Istio 和 Kuma 等。</p> <p>APISIX-Mesh-Agent 作为 Apache APISIX 的协议适配器,实现了 Envoy xDS 协议,将数据从 xDS 格式转换为 Apache APISIX 兼容的格式。</p> <p>同时其实现了 ETCD V3 API 使得在 Apache APISIX 看来,它就是一个 ETCD 集群,由于 Apache APISIX 对 ETCD 的原生支持, 配置即可顺利地从某个服务网格控制面下发到 Apache APISIX 。</p> <p>得益于 Apache APISIX 的良好设计,基于 Apache APISIX 的服务网格方案性能更佳、资源占用更少、二次开发和定制的成本更低(Lua 语言上手容易,且 Apache APISIX 已经支持使用多语言进行插件开发),并且由于兼容 xDS 协议,从 Istio、Kuma 等网格方案进行迁移也更加平滑。</p> <h2>下载</h2> <p>下载 APISIX-Mesh-Agent 0.6-Release 源代码及二进制安装包,请访问下载页面。 <code>https://github.com/api7/apisix-mesh-agent/releases/tag/0.6</code></p> <h2>文档更新</h2> <p>在本次发布过程中,我们也在持续更新和发布新的使用文档,欢迎大家提出宝贵的意见。 <code>https://github.com/api7/apisix-mesh-agent/tree/main/docs</code></p>

Apache APISIX Ingress Controller 中的流量切分

<p>流量切分(traffic split)是指将流量按照定义好的规则和比例分摊到多个后端服务,像常见的 API 网关产品(例如 <a href="https://apisix.apache.org/">Apache APISIX</a>,<a href="https://traefik.io/">Traefik</a>)、服务网格 Sidecar Proxy(例如 <a href="https://envoyproxy.io/">Envoy</a>,<a href="https://github.com/linkerd/linkerd2-proxy">linkerd2-proxy</a>),都提供了流量切分的功能,以此来实现细粒度的 <a href="https://blog.getambassador.io/cloud-native-patterns-canary-release-1cb8f82d371a">金丝雀发布</a>,<a href="https://martinfowler.com/bliki/BlueGreenDeployment.html">蓝绿部署</a> 等功能。</p> <p>作为 <a href="https://kubernetes.io/">Kubernetes</a> 集群流量入口,<a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">Ingress Controller</a> 自然也需要支持流量切分的功能,在后端应用需要发布时,能够提供逐步切流,回滚的能力,降低应用发布带来的风险。本文先后介绍了 <a href="https://kubernetes.github.io/ingress-nginx/">Ingress Nginx</a> 和 <a href="https://github.com/Kong/kubernetes-ingress-controller">Kong Ingress Controller</a> 中提供的流量切分功能(有时也称为金丝雀发布),之后介绍了流量切分在 <a href="https://github.com/apache/apisix-ingress-controller">Apache APISIX Ingress Controller</a> 中的实现。</p> <p>(注:为了描述方便,下文用术语 “灰度应用” 表示命中金丝雀发布规则后对应的后端应用和术语“稳定应用”表示金丝雀发布规则未命中时对应的后端应用。例如,在下图中,灰度应用是 “foo-canary”,稳定应用是 “foo”。)</p> <p><img src="https://static.apiseven.com/202108/pasted%20image%202.png" alt="1.png" referrerpolicy="no-referrer"></p> <h2>Ingress Nginx</h2> <p><a href="https://kubernetes.github.io/ingress-nginx/">Ingress Nginx</a> 提供了<a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary">金丝雀发布</a>的功能,我们可以为 <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a> 资源添加 nginx.ingress.kubernetes.io/canary: “true” 注解来启用该功能,Ingress Nginx 支持使用以下几个注解来自定义金丝雀发布的规则。</p> <ul> <li>nginx.ingress.kubernetes.io/canary-by-header</li> </ul> <p>通过某个请求头的值来判断流量应该被转发到灰度应用(值为 always)还是稳定应用(值为 never)。</p> <ul> <li>nginx.ingress.kubernetes.io/canary-by-header-value</li> </ul> <p>该注解扩展了 nginx.ingress.kubernetes.io/canary-by-header,通过判断指定请求头的值是否与该注解的值匹配,来决定流量的去向(匹配则转发到灰度应用,否则转发到稳定应用)。</p> <ul> <li>nginx.ingress.kubernetes.io/canary-by-header-pattern</li> </ul> <p>该注解和 nginx.ingress.kubernetes.io/canary-by-header 类似,只是匹配采用了 <a href="https://www.pcre.org/">PCRE</a> 兼容的正则表达式。</p> <ul> <li>nginx.ingress.kubernetes.io/canary-by-cookie</li> </ul> <p>通过 Cookie 中某个字段的值来判断流量应该被转发到灰度应用(值为 always)还是稳定应用(值为 never)。</p> <ul> <li>nginx.ingress.kubernetes.io/canary-weight</li> </ul> <p>为灰度应用设定一个大小位于 [0, 100] 的权重,流量将按照权重在灰度应用和稳定应用之间分配。权重为 0 则所有流量都会被转发到稳定应用;权重为 100 则所有流量都会被转发到灰度应用。</p> <p>下图的例子将携带 User-Agent 头部匹配 “.<em>Mozilla.</em>” 模版,URI path 前缀为 /get 的请求转发到灰度应用 foo-canary。</p> <pre><code>apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "User-Agent" nginx.ingress.kubernetes.io/canary-by-header-pattern: ".*Mozilla.*" name: ingress-v1beta1 </code></pre> <h2>Kong</h2> <p>Kong 提供了金丝雀发布的插件,并且通过 KongPlugin 这个 CRD 资源将该功能暴露到了 Kong Ingress Controller 中。管理员/用户首先需要创建一个 KongPlugin 对象,填入金丝雀发布的规则,然后在目标 <a href="https://kubernetes.io/docs/concepts/services-networking/service/">Kubernetes Service</a> 中加入注解 konghq.com/plugins 并赋予该对象的名称;亦或是创建一个 KongClusterPlugin 对象,进而使得该插件在集群内生效。</p> <pre><code>apiVersion: configuration.konghq.com/v1 kind: KongPlugin metadata: name: foo-canary config: percentage: 30 upstream_host: foo.com upstream_fallback: false upstream_port: 80 plugin: canary --- apiVersion: v1 kind: Service metadata: name: foo-canary labels: app: foo annotations: konghq.com/plugins: foo-canary spec: ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: foo canary: true </code></pre> <p>上述例子将 foo-canary 这个服务标记为灰度应用,并为其建立了一条金丝雀发布规则,要求 30% 的流量转发到该应用。</p> <h2>Apache APISIX</h2> <p><a href="https://apisix.apache.org/">Apache APISIX</a> 提供的 <a href="https://apisix.apache.org/docs/apisix/plugins/traffic-split">traffic-split</a> 插件支持配置自定义规则进行流量切分。Apache APISIX Ingress Controller 在此基础之上,结合 <a href="https://apisix.apache.org/docs/ingress-controller/concepts/apisix_route">ApisixRoute</a> 灵活的路由规则配置,将流量切分实现为了 <a href="https://apisix.apache.org/docs/ingress-controller/concepts/apisix_route">ApisixRoute</a> 中的第一类功能(无须通过注解定义)。</p> <h3>基于权重</h3> <p>基于权重的流量切分可以通过为单条路由规则配置多个 Kubernetes Service 后端来实现,如:</p> <pre><code>apiVersion: apisix.apache.org/v2alpha1 kind: ApisixRoute metadata: name: foo-route spec: http: - name: rule1 match: hosts: - foo.org paths: - /get* backends: - serviceName: foo-canary servicePort: 80 weight: 10 - serviceName: foo servicePort: 80 weight: 5 </code></pre> <p>上述示例将 ⅔ 的满足 Host 为 foo.org,URI path 前缀为 /get 的请求转发到了 foo-canary 这个 service,剩下 ⅓ 的请求将被路由到 foo。</p> <p>在实际应用中,可以为灰度应用设定较小的权重,进行小规模的验证,确认没有问题后修改 ApisixRoute 资源,逐步放大其权重,最终将流量全部转发到该灰度应用,完成发布。</p> <h3>基于规则</h3> <p>ApisixRoute 资源允许用户添加路由匹配表达式 - <a href="https://github.com/apache/apisix-ingress-controller/blob/master/docs/en/latest/concepts/apisix_route.md#advanced-route-features">Exprs</a> 字段,来自定义路由匹配;此外,单个 ApisixRoute 资源允许插入多条路由规则,因此基于规则的流量切分被 Apache APISIX Ingress Controller 以一种无缝的方式集成。</p> <pre><code>apiVersion: apisix.apache.org/v2alpha1 kind: ApisixRoute metadata: name: foo-route spec: http: - name: rule1 priority: 1 match: hosts: - foo.org paths: - /get* backends: - serviceName: foo servicePort: 80 - name: rule2 priority: 2 match: hosts: - foo.org paths: - /get* exprs: - subject: scope: Query name: id op: In set: - "3" - "13" - "23" - "33" backends: - serviceName: foo-canary servicePort: 80 </code></pre> <p>上述示例,将满足 Host 为 foo.org,URI path 前缀为 /get 的请求,分为两部分:</p> <ul> <li> <p>id 参数是 3、13、23、33 其中之一,这部分请求将命中路由规则 rule2,从而被转发到 foo-canary 这一服务;</p> </li> <li> <p>其他请求将命中路由规则 rule1,从而被转发到 foo 这一服务。</p> </li> </ul> <h2>总结</h2> <p>Ingress Nginx 支持基于权重和基于 Header 规则的金丝雀发布,但是需要通过 annotations 的方式进行配置,语义不强;而 Kong 的方案仅支持基于权重进行金丝雀发布,某些场景下无法满足使用需求,且需要多处配置;Apache APISIX Ingress Controller 则较好地同时支持了两种使用场景,并且其提供的路由规则灵活多变,配置简单且易于理解。</p>

使用 Cypress 获取前端测试覆盖率

<h2>背景</h2> <p>在<a href="https://www.apiseven.com/blog/stable-product-delivery-with-cypress">《使用 Cypress 让产品持续稳定交付》</a>文章中,我们讨论了为什么选用 Cypress 作为 E2E 测试框架。在花了近两个月时间完善测试案例后,我们需要测试覆盖率来量化测试覆盖是否足够。本文将介绍如何使用 Cypress 获取 APISIX Dashboard 前端 E2E 覆盖率。</p> <h2>什么是代码覆盖率</h2> <p>代码覆盖是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。测试代码覆盖率一定程度上反映了代码的健康程度。</p> <h2>安装依赖&amp;配置</h2> <p>想要收集测试覆盖数据,我们需要将原有的业务代码放一些探针以供 Cypress 收集数据。</p> <p>Cypress 官方推荐两种方式,第一种是通过 nyc 生成临时目录,把已经写入探针的代码运行起来以收集测试覆盖数据。第二种方法是通过代码转换管道实时做代码转换,这就少去了临时文件夹烦恼,使得收集测试覆盖率数据比较清爽。我们选择第二种方式收集前端 E2E 覆盖率。</p> <ol> <li>安装依赖</li> </ol> <pre><code class="language-shell">yarn add babel-plugin-istanbul --dev </code></pre> <ol start="2"> <li>安装 cypress 插件</li> </ol> <pre><code class="language-shell">yarn add @cypress/code-coverage --dev </code></pre> <ol start="3"> <li>配置 babel</li> </ol> <pre><code class="language-ts">// web/config/config.ts extraBabelPlugins: [ ['babel-plugin-istanbul', { "exclude": ["**/.umi", "**/locales"] }], ], </code></pre> <ol start="4"> <li>配置 Cypress code coverage 插件</li> </ol> <pre><code class="language-javaScript">// web/cypress/plugins/index.js module.exports = (on, config) =&gt; { require('@cypress/code-coverage/task')(on, config); return config; }; </code></pre> <pre><code class="language-javaScript">// web/cypress/support/index.js import '@cypress/code-coverage/support'; </code></pre> <ol start="5"> <li>获取测试覆盖率</li> </ol> <p>配置完毕后,需要我们运行测试案例,在测试案例运行结束后,Cypress 会生成 coverage 和 .nyc_output 文件夹,里面包含测试覆盖率的报告。</p> <p><img src="https://static.apiseven.com/202108/pasted%20image%200.png" alt="1.png" referrerpolicy="no-referrer"></p> <p>执行下面的命令后测试覆盖率信息会出现在控制台。</p> <pre><code class="language-shell">npx nyc report --reporter=text-summary </code></pre> <p><img src="https://static.apiseven.com/202108/pasted%20image%201.png" alt="2.png" referrerpolicy="no-referrer"></p> <p>在 coverage 目录下,会有更详细的报告网页,如图所示:</p> <p><img src="https://static.apiseven.com/202108/pasted%20image%203.png" alt="3.png" referrerpolicy="no-referrer"></p> <ul> <li> <p>Statements 表示是否每个语句都执行了</p> </li> <li> <p>Branchs 表示是否每个 if 代码块都执行了</p> </li> <li> <p>Functions 表示是否每个函数都调用了</p> </li> <li> <p>Lines 表示是否每一行都执行了</p> </li> </ul> <h2>总结</h2> <p>测试覆盖率的高低一定程度上反映了项目的质量。目前 APISIX Dashboard 前端 E2E 覆盖率已经达到了 71.57%, 我们将持续协同社区,继续增强测试覆盖率,为用户提供更可靠稳定的产品。</p>

使用 Helm Charts 安装 Apache APISIX

<p>日前<a href="https://www.apiseven.com/">支流科技</a>提供了一个在线 Helm Charts 仓库 https://charts.apiseven.com, 用户可通过该仓库轻松安装 Apache APISIX、Apache apisix-dashboard 和 Apache apisix-ingress-controller (而不需要提前 clone 对应的项目)。</p> <h2>如何使用</h2> <p>只需通过以下几步即可安装 Apache APISIX 2.1 版本。</p> <ol> <li> <p>添加仓库并获取更新</p> <pre><code>$ helm repo add apisix https://charts.apiseven.com $ helm repo update </code></pre> </li> </ol> <!-- markdown-link-check-enable --> <ol start="2"> <li> <p>查看仓库中可用的 Charts 包</p> <pre><code>$ helm search repo apisix NAME CHART VERSION APP VERSION DESCRIPTION apisix/apisix 0.1.2 2.1.0 A Helm chart for Apache APISIX apisix/apisix-dashboard 0.1.0 2.3.0 A Helm chart for Apache APISIX Dashboard </code></pre> </li> <li> <p>安装 Apache APISIX 到目标 Kubernetes 集群中</p> <pre><code>$ helm install apisix-gw apisix/apisix --namespace default NAME: apisix-gw LAST DEPLOYED: Fri Feb 19 11:34:14 2021 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Get the application URL by running these commands: export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gw-gateway) export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT </code></pre> </li> </ol> <h2>另请参阅</h2> <ul> <li>https://github.com/apache/apisix-helm-chart</li> </ul>

使用 Cypress 让产品持续稳定交付

<h2>背景</h2> <p>Apache APISIX Dashboard 的设计是为了让用户通过前端界面尽可能方便地操作 Apache APISIX。从项目初始化到现在,已经有 552 commits、发布了 10 个版本。在如此之快的产品迭代过程中,确保开源产品质量显的尤为重要。为此,我们引入了 E2E 测试模块以确保稳定的产品交付。</p> <h2>什么是前端 E2E</h2> <p>E2E,是“End to End”的缩写,可以翻译成“端到端”测试。它模仿用户操作行为,从某个入口开始,逐步执行操作,直到完成某项工作。完善的测试可以防止代码改动时破坏原有的逻辑。</p> <h2>为什么选择 Cypress</h2> <p>我们在选型调研期分别使用 Taiko、Puppeteer、TestCafe 、Cypress 书写创建路由的测试案例,通过使用每个测试框架书写案例,来体会其各自的特点。</p> <p>Taiko 的特点是具有 smart selector, 可以根文字内容、位置关系智能定位想要操作的元素 ,上手成本也比较低,能够很快的完成测试案例。但是,在书写测试案例时并不友好,当用户误操作退出终端后,所书写的测试案例也全部丢失。如果想要完整地运行测试案例,还需要配合其它的 test runner 一起使用,这无疑又增加了用户的学习成本。</p> <p>Puppeteer 具有最好的性能表现。但是,测试并不是 Puppeteer 的重点。它被广泛用于网页爬虫。我们的项目起初使用的是 Ant Design 官方推荐的 E2E 测试框架即 Puppeteer ,使用了一段时间后发现 Puppeteer 对非前端开发者不是那么友好,很难让其他用户参与进来。当用户编写测试案例时,缺少智能元素定位的功能加持使得用户学习曲线很高。</p> <p>TestCafe 的安装简单程度令人惊喜,它具有内置的等待机制,用户不用主动的去 sleep 等待页面交互,并且支持多浏览器并发测试,这对多浏览器兼容性测试很有帮助。缺点是它的调试过程并不是那么友好,每次测试案例更改后都要从新跑一遍用例。对于开发人员而言,要有一定的 Javascript 语法基础。其次,它的运行速度相对于其它几个框架而言比较慢,尤其是执行 withText() 查找元素时。</p> <p>综合比较后,我们最终选用了 Cypress 作为我们的前端 E2E 框架,列出四点主要原因:</p> <ol> <li>语法简单</li> </ol> <p>Cypress 测试所使用的语法非常简单,而且容易阅读和书写。稍加练习后,就能掌握创建测试案例,这对于开源项目来说是很重要的,因为这样可以让社区里有兴趣参加 E2E 测试案例的用户以最低的学习成本参与到书写测试案例中。</p> <ol start="2"> <li>易于调试</li> </ol> <p>在调试测试用例时,我们可以使用 Cypress 的 Test Runner。Test Runner 可以展示多维度的数据,通过这些信息,我们可以快速定位到问题所在。</p> <ul> <li>展示用例执行状态,包括成功、失败、运行中的个数;</li> <li>展示整个测试集执行的总时间;</li> <li>内置 Selector Playground 可以帮助定位元素;</li> <li>展示了每一个用例的每一个执行步骤,并形成快照,在执行完毕后,可以把每一个执行步骤的信息展示出来;</li> </ul> <ol start="3"> <li>社区活跃</li> </ol> <p>Cypress 有一个庞大的用户社区,社区里面总是有很多人在分享他们的经验和主意。</p> <p>这在遇到问题时很有帮助,你很有可能遇到别人在之前就已经遇到的问题。另外,当有新的功能需求时,我们也可以参与到社区,通过讨论,把自己想加入的特性加入到 Cypress 中,就像我们 Apache APISIX 社区做的事一样:听取社区的意见并反哺社区。</p> <ol start="4"> <li>文档清晰</li> </ol> <p>Cypress 的文档结构更加清晰全面。在使用初期,我们根据官方文档指引很快的就能把 Cypress 引入到我们的项目中并书写第一个案例。此外,在其文档站中,有大量的文档可供参阅,这会给用户很好的指引,会让用户知道怎么样做才是最好的实践。</p> <h2>Cypress 与 APISIX Dashboard</h2> <p>目前 APISIX Dashboard 已经书写 49 个测试案例。我们在 GitHub Action 中配置对应的 CI,确保每次合并代码前测试通过以保证代码质量。我们参考 Cypress 的最佳实践并结合我们的项目,和大家分享一下 Cypress 在 APISIX Dashboard 的使用。</p> <p><img src="https://static.apiseven.com/202108/apisix-dashboard-e2e.gif" alt="图片" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202102/image.png" alt="图片" referrerpolicy="no-referrer"></p> <ol> <li>常用的功能封装成命令。</li> </ol> <p>以登录为例,登录是进入系统必不可少的环节, 我们把登录环节封装成命令,这样,在每次运行案例之前,进行登录命令调用。</p> <pre><code class="language-javascript">Cypress.Commands.add("login", () =&gt; { cy.request( "POST", 'http://127.0.0.1/apisix/admin/user/login', { username: "user", password: "user", } ).then((res) =&gt; { expect(res.body.code).to.equal(0); localStorage.setItem("token", res.body.data.token); }); }); </code></pre> <pre><code class="language-javascript">beforeEach(() =&gt; { // init login cy.login(); }); </code></pre> <ol start="2"> <li>将 selector 和 data 提取成公共变量。</li> </ol> <p>为了让用户更直观理解测试代码的含义,我们将 selector 和 data 抽成公共变量。</p> <pre><code class="language-javascript">const data = { name: "hmac-auth", deleteSuccess: "Delete Plugin Successfully", }; const domSelector = { tableCell: ".ant-table-cell", empty: ".ant-empty-normal", refresh: ".anticon-reload", codemirror: ".CodeMirror", switch: "#disable", deleteBtn: ".ant-btn-dangerous", }; </code></pre> <ol start="3"> <li>移除 cy.wait(someTime)</li> </ol> <p>我们在使用 Cypress 的前期使用 cy.wait(someTime),但在使用中发现,cy.wait(someTime) 过度依赖网络环境以及测试机器的性能,当网络环境或者机器性能差时,会导致测试案例报错。推荐的做法是配合 cy.intercept() 使用以明确指定需要等待的网络资源。</p> <pre><code class="language-javascript">cy.intercept('https://apisix.apache.org/').as('fetchURL'); cy.wait('@fetchURL'); </code></pre> <h2>总结</h2> <p>目前 APISIX Dashboard 已经书写 49 个测试案例。未来,我们将持续增强前端 E2E 覆盖率,在社区中约定每次提交新的特性或者 bugfix 都需要书写对应的测试案例以保证产品的稳定性。</p> <p>欢迎大家加入我们一起打磨世界级的网关产品。</p> <p>项目地址:<a href="https://github.com/apache/apisix-dashboard">https://github.com/apache/apisix-dashboard</a></p>

Amazon EKS 和 APISIX ingress controller 如何配合使用来管理复杂流量

<h2>背景</h2> <p>作为流量管理人员,有时候,即使做好了万全准备,也难挡突发状况的出现。构建四通八达的 API 网关至关重要!今天就来为大家介绍**Amazon EKS + Ingress APISIX!**是如何帮助我们解决这个问题的。</p> <h2>简介</h2> <p>Kubernetes 是一个开源系统,用于自动化容器化应用程序的部署、扩展和管理。<strong>Amazon Elastic Kubernetes Service(Amazon EKS)作为一种托管的 Kubernetes 服务,您可以在亚马逊云科技上轻松运行 Kubernetes 负载而无需对控制平面或节点进行安装和维护。</strong></p> <p><strong>Apache APISIX 是一个动态、实时、高性能的 API 网关。它提供了丰富的流量管理功能,如负载平衡、动态上游、灰度部署、流量分割、身份验证和可观测性等</strong>。您可以使用 Apache APISIX 处理传统客户端和服务器之间的南北流量以及服务之间的东西流量。</p> <p>Ingress APISIX 可以将 Apache APISIX 作为 Kubernetes 的入口控制器使用,从而为 Kubernetes 引入 Apache APISIX 的各项优秀功能。借助妥善设计的 Controller 组件的驱动,可以帮助用户满足复杂的流量管理需求。</p> <p><img src="https://static.apiseven.com/202102/WechatIMG3731.jpeg" alt="apisix-ingress-controller技术架构" referrerpolicy="no-referrer"></p> <h2>如何在 Amazon Elastic Kubernetes Service 上配置和运行 Ingress APISIX。</h2> <h3><strong>前提要求</strong></h3> <p>准备运行前,请在亚马逊云科技上配置好可用的 Amazon EKS 集群。</p> <p>你自己的环境中应具备 kubectl 工具,请运行如下命令将上下文设置为自己的 Amazon EKS 集群:</p> <pre><code>aws eks update-kubeconfig --name &lt;your eks cluster name&gt; --region &lt;your region&gt; </code></pre> <p>Kubernetes 集群就绪后,创建名为 ingress-apisix 的名称空间,后续用到的所有资源都将创建于该名称空间中。</p> <pre><code>kubectl create namespace ingress-apisix </code></pre> <p>我们将使用 Helm 部署 Ingress APISIX(Apache APISIX 和 apisix-ingress-controller)的所有组件,<a href="https://helm.sh/docs/intro/install/">因此也请按照安装指南</a> 来安装 Helm。适用于 Apache APISIX 和 apisix-ingress-controller 的 helm chart 位于 <a href="https://github.com/apache/apisix-helm-chart">apache/apisix-helm-chart</a> 和 <a href="https://github.com/apache/apisix-ingress-controller">apache/apisix-ingress-controller</a> 路径下,请克隆这些路径以获得相应的 chart。</p> <h3><strong>安装 Apache APISIX</strong></h3> <p>Apache APISIX 充当了apisix-ingress-controller的代理平面,应提前部署完成。</p> <pre><code>cd /path/to/apisix-helm-chart helm repo add bitnami https://charts.bitnami.com/bitnami helm dependency update ./chart/apisix helm install apisix ./chart/apisix \ --set gateway.type=LoadBalancer \ --set allow.ipList="{0.0.0.0/0}" \ --namespace ingress-apisix kubectl get service --namespace ingress-apisix </code></pre> <p>上述命令将创建两个 Kubernetes Service 资源,一个为负责处理真实流量的 apisix-gateway,另一个为充当控制平面并处理所有配置改动的 apisix-admin。此处我们将 apisix-gateway 创建为 LoadBalancer 类型的服务,可借助 Amazon Network Load Balancer 将其暴露至互联网。我们可通过下列命令找到负载均衡器的主机名:</p> <pre><code>kubectl get service apisix-gateway \ --namespace ingress-apisix \ -o jsonpath='{.status.loadBalancer.ingress[].hostname}' </code></pre> <p>另外要注意:allow.ipList 字段应根据我们自己 Amazon EKS 集群中的 EKS CIDR Ranges 进行定制,这样 apisix-ingress-controller 即可由 Apache APISIX 进行授权(用于推送资源)。</p> <p>如果还有其他需求,请参阅 <a href="https://github.com/apache/apisix-helm-chart/blob/master/charts/apisix/values.yaml">value.yaml</a> 进一步了解所有配置项。</p> <h3><strong>安装 apisix-ingress-controller</strong></h3> <p>成功部署 Apache APISIX 后,需要安装 Controller 组件了。</p> <pre><code>cd /path/to/apisix-ingress-controller # install base resources, e.g. ServiceAccount. helm install ingress-apisix-base -n ingress-apisix ./charts/base # install apisix-ingress-controller helm install ingress-apisix ./charts/ingress-apisix \ --set ingressController.image.tag=dev \ --set ingressController.config.apisix.baseURL=http://apisix-admin:9180/apisix/admin \ --set ingressController.config.apisix.adminKey={YOUR ADMIN KEY} \ --namespace ingress-apisix </code></pre> <p>ingress-apisix-base chart 会为 apisix-ingress-controller 安装一些基本依赖项,例如 ServiceAccount 及其专用 CRD 等内容。</p> <p>ingress-apisix chart 将引导我们安装 Controller 自身,我们可以将 image 标签更改为所需的发布版本,并可更改上述命令中 ingressController.config.apisix.adminKey 的值,这些配置可根据实际场景进行调整(并确保此处使用的 Admin key 与 Apache APISIX 部署中所用的 Key 相同)。如果还有其他需求,可以参阅 value.yaml 进一步了解所有配置项。</p> <p>随后试着打开 <strong>Amazon EKS 控制台</strong>,选择自己的集群并单击 Workloads 标签,所有将能看到 Apache APISIX 的所有 Pod、etcd 以及 apisix-ingress-controller 均已就绪。</p> <h3><strong>开始测试</strong></h3> <p>至此我们已经部署了 Ingress APISIX 的所有组件,请务必验证一切均可正常运行。随后我们将部署一个 httpbin 服务并要求 Apache APISIX 将所有到”local.httpbin.org”主机的请求路由至该服务。</p> <p>为此,我们首先需要创建 httpbin 工作负载并将其暴露出来。</p> <pre><code>kubectl run httpbin --image kennethreitz/httpbin --port 80 kubectl expose pod httpbin --port 80 </code></pre> <p>为了让 Apache APISIX 对请求进行正确的路由,我们需要创建一个 ApisixRoute 资源驱动这一过程。</p> <pre><code># ar-httpbin.yaml apiVersion: apisix.apache.org/v1 kind: ApisixRoute metadata: name: httpserver-route spec: rules: - host: local.httpbin.org http: paths: - backend: serviceName: httpbin servicePort: 80 path: /* </code></pre> <p>上述 ApisixRoute 资源会让 Apache APISIX 将主机头为“local.httpbin.org”的请求路由至(我们刚刚创建的)httpbin 后端。</p> <p>随后应用该设置,但请注意:该服务和 ApisixRoute 资源应放置在同一个名称空间中,apisix-ingress-controller 不允许跨越名称空间。</p> <pre><code>kubectl apply -f ar-httpbin.yaml </code></pre> <p>从可触达 Apache APISIX 服务的任意位置通过一个简单的 curl 调用测试结果。</p> <pre><code>$ curl http://{apisix-gateway-ip}:{apisix-gateway-port}/headers -s -H 'Host: local.httpbin.org' { "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.1", "X-Amzn-Trace-Id": "Root=1-5ffc3273-2928e0844e19c9810d1bbd8a" } } </code></pre> <p>如果 Serivce 类型为 ClusterIP,则需要登录到 Amazon EKS 集群中的一个 Pod,随后使用 ClusterIP 或 Service FQDN 访问 Apache APISIX。如果已经暴露(无论暴露了 NodePort或 LoadBalancer),则可直接访问可触达的外部端点。</p> <p>注:该文章转自 aws 微信公众号。</p>

Apache APISIX 助力企业数字化转型

<h2>背景</h2> <p>随着互联网、大数据、云计算、人工智能、物联网、区块链等一系列技术创新与行业服务的加速融合,行业产品创新能力正在不断提升,数字化、智能化不断催生新兴的服务模式和新产品,已成为企业数字化转型升级的新方向。</p> <p>企业用户采购 API 网关作为企业自身数字化转型的一环,为后续持续转型和升级做铺垫,逐步向“全面云化”、“分布式化”、“智能化”、“开放化”的新技术体系演进。云原生 API 网关 Apache APISIX,可以完美的助力企业完成 API 管理和微服务架构转型。此外基于开源 APISIX 还有 k8s Ingress Controller 和 Service Mesh 产品,企业用户后续升级到 Kubernetes 和服务网格,提供统一、顺滑的过渡方案。底层技术栈的统一,极大降低学习、管理维护成本。</p> <p>同时,在云原生架构下,开源 + 购买商业支持的方式是首选,这不仅仅保证企业自身不被具体商业公司锁定,更重要的是,可以让企业保持快速的技术升级,以便让技术满足产品的迭代, 最终在竞争中取得优势。</p> <p>APISIX 是 Apache 基金会的顶级项目,同时也在云原生软件基金会(CNCF)的全景图中,与 Apache 基金会旗下的各种大数据组件和 CNCF 的各种云原生组件,有着非常好的生态,可以方便的对接企业用户在大数据和云原生领域的其他系统。</p> <p>目前已有众多国内外知名互联网和传统公司采用 &nbsp;Apache APISIX,使用案例分布在金融、电信运营商、物联网、零售、在线教育、航空航天等多个行业,包括 NASA、航天网信、欧盟数字工厂、思必驰、中国移动、腾讯、虎牙、奈雪的茶、雪球、空中云汇、泰康人寿、作业帮、嘀嗒出行、明源云等。</p> <p>深圳支流科技是 Apache APISIX 的原厂,初始团队来自中国知名互联网公司,投资方包括真格基金和真成投资,可以提供本地化和专业的服务。</p> <h2>API 网关典型需求</h2> <table> <thead> <tr> <th><strong>需求</strong></th> <th><strong>说明</strong></th> <th><a href="https://github.com/apache/apisix">APISIX</a><strong>支持情况</strong></th> </tr> </thead> <tbody> <tr> <td><strong>控制台</strong></td> <td>支持操作人员在控制台上操作,完成 API 管理、监控等需要</td> <td><a href="https://github.com/apache/apisix">APISIX</a> 提供了方便使用的 dashboard,支流科技在开源 APISIX 的基础上增加了用户、角色、工作区、多集群等企业级功能。</td> </tr> <tr> <td><strong>用户认证</strong></td> <td>支持 AK/SK、JWT 用户认证</td> <td><a href="https://github.com/apache/apisix">APISIX</a> 支持 Consumer,可以直接完成用户认证,除了常见 AK/SK、JWT 外,还支持 Basic-Auth、Key-Auth 等多种用户认证。</td> </tr> <tr> <td><strong>动态路由、上游</strong></td> <td>动态添加修改路由、上游</td> <td>在 APISIX 中路由、上游的添加修改是原生动态的,内存生效不需要任何进程替换,相比 Nginx 等需要 reload 的实现方案要好,不会产生流量抖动。</td> </tr> <tr> <td><strong>负载均衡器</strong></td> <td>反向代理支持指定不同负载均衡算法</td> <td>除了已支持带有权重的 roundrobin 和一致性哈希算法外,还支持指数加权移动平均法(EWMA)和最小连接数算法。<br><a href="https://github.com/apache/apisix">APISIX</a> 是目前<strong>唯一</strong>支持用户自定义负载均衡策略的 API 网关,满足企业用户的一些特殊场景需求。</td> </tr> <tr> <td><strong>限流、限速熔断</strong></td> <td>为保护上游对请求流量做频度控制</td> <td>支持单机和集群两种方式限流限速。<br>单机主要有三种:<br>1. limit-count:指定时间限制请求总数。<br>2. limit-req:限制请求频率。<br>3. limit-conn:限制并发数。<br>集群支持两种:<br>limit-count 和 limit-conn。</td> </tr> <tr> <td><strong>监控告警</strong></td> <td>对 API 请求、API 网关自身做监控,支持设置阈值完成告警</td> <td>监控,目前已支持下面三种类型:<br>1. Metric:支持 <a href="https://prometheus.io/">Prometheus</a> 对接。<br>2. Logging:将访问日志(动态自定义)发送到指定 HTTP、TCP 或 UDP 服务。<br>3. Tracing: <a href="https://opentracing.io/">OpenTracing</a> 协议,支持 <a href="https://skywalking.apache.org/">SkyWalking</a> 、 <a href="https://zipkin.io/">Zipkin</a> 等实现。<br>告警:<br>1. 支持任何 HTTP 或 syslog 方式发送告警信息。<br>2. 支持插件方式完成企业自定义。</td> </tr> <tr> <td><strong>上游健康检查</strong></td> <td>剔除不健康节点</td> <td>检查方式支持下面两种:<br>1. 主动健康检查:主动探测上游服务是否健康,剔除不健康节点。<br>2. 被动健康检查:根据请求应答码更新节点状态,剔除不健康节点。</td> </tr> <tr> <td><strong>多集群管理</strong></td> <td>管理控制台统一登录,同时管理不同集群的 API</td> <td>在 <a href="https://www.apiseven.com/apisix-vs-api7">API7</a> 支持多集群:不同集群有完全独立的 API 和资源。企业不同主要业务线,为了避免相互干扰,优先推荐集群方式隔离。</td> </tr> <tr> <td><strong>API 编排</strong></td> <td>通过一个请求来调用多个微服务,并对返回结果做数据处理,最终整合成一个结果</td> <td>支持两种方式完成此特性:<br>1. 通过 <a href="https://graphql.org/">GraphQL</a> 方式直接完成 API 的编排,目前 APISIX 已经开源支持。<br>2. 通过自定义插件方式扩充满足企业 API 编排需求。</td> </tr> <tr> <td><strong>swagger 格式 API 导入</strong></td> <td>使用 <a href="https://swagger.io/">swagger</a> 标准完成 API 批量导入</td> <td>已内置支持 <a href="https://swagger.io/specification">OpenAPI Specification3.0</a> 方式导入 <a href="https://swagger.io/">swagger</a> 。</td> </tr> </tbody> </table> <h2>关于 Apache APISIX</h2> <p><strong>1. Apache 顶级项目</strong></p> <p>Apache APISIX 是中国最快毕业的孵化器项目,也被纳入了 CNCF 全景图中。</p> <p><img src="https://static.apiseven.com/logo/20210203/1.png" alt="1.png" referrerpolicy="no-referrer"></p> <p>目前很多流行的 AI、大数据类开源项目,主要集中在 Apache 基金会(300 多顶级项目)。APISIX 与这些项目具有相同的文化认同,更容易完成生态对接(如下图)。</p> <p><img src="https://static.apiseven.com/logo/20210203/2.png" alt="2.png" referrerpolicy="no-referrer"></p> <p><strong>2. CNCF 基金会生态</strong></p> <p>APISIX 的配置中心使用 etcd ,更适合云原生技术架构,降低用户使用和运维成本。</p> <p><img src="https://static.apiseven.com/logo/20210203/3.png" alt="3.png" referrerpolicy="no-referrer"></p> <p><a href="https://www.apiseven.com/">支流科技</a>还捐献了 APISIX Ingress Controller 到 Apache 基金会,为 Kubernetes 流量入口转发多一个选择。APISIX 也支持 <a href="https://helm.sh/">helm chart</a> 方式安装,简化 Kubernetes 环境内 APISIX 部署。</p> <p><strong>3. 全世界最活跃 API 网关项目</strong></p> <p>Apache APISIX 是全球最活跃的开源 API 网关项目,在中国所有开源项目中排名第 22 位。参考维度(如下图)有活跃开发者数量、活跃 PR 和 Issue 数量等。</p> <p><img src="https://static.apiseven.com/logo/20210203/4.png" alt="4.png" referrerpolicy="no-referrer"></p> <p><strong>4. 智能面,插件编排</strong></p> <p>开发者可以使用 DAG(有向无环图)对插件进行编排,通过决策树对请求流量进行实时分析和处理,以此实现了低代码 API 网关。</p> <p>用户在界面上通过拖拉拽方式,自由编排插件执行顺序,让产品经理或运维可以直接完成业务需求。利用该特性,可以让 APISIX 直接与智能 AI 服务配合。</p> <p><img src="https://static.apiseven.com/logo/20210203/5.png" alt="5.png" referrerpolicy="no-referrer"></p> <p>插件编排是 APISIX 独创,<strong>是全球唯一支持插件编排的智能 API 网关</strong>。文档不支持动画,可以在支流科技官网查看动画演示:<a href="https://www.apiseven.com/">www.apiseven.com</a> 。</p> <p><strong>5. 架构优势,内置高可用</strong></p> <p>APISIX 是一整套完整的 API 网关解决方案,不仅提供数据面(Data Plane)实现,也提供了控制面(Control Plane)实现,二者均为无状态设计,可以根据需求自由扩缩容。</p> <p><img src="https://static.apiseven.com/logo/20210203/6.png" alt="6.png" referrerpolicy="no-referrer"></p> <p><strong>6. 路由、上游、SSL 等对象全部支持动态指定</strong></p> <p>与传统 HAProxy、Nginx、Spring Cloud Gateway、Zuul 等不支持动态路由的方案相比, APISIX 的所有对象全部是内存动态添加或更新,数据替换或更新时不会产生任何进程重启,最小化影响线上流量。</p> <p><strong>7. 性能指标最好 API 网关</strong></p> <p>APISIX 是目前所有开源 API 网关产品中性能技术指标表现最好,毫秒级低延迟,满足企业用户对 API 的实时性要求。</p> <p><strong>8. 使用案例</strong></p> <p>目前已有众多国内外知名互联网和传统行业公司采用 &nbsp;Apache APISIX,使用案例分布在金融、电信运营商、物联网、零售、在线教育、航空航天等多个行业,包括 NASA、航天网信、欧盟数字工厂、思必驰、中国移动、腾讯、虎牙、奈雪的茶、雪球、空中云汇、作业帮、嘀嗒出行、明源云等。</p> <p><img src="https://static.apiseven.com/logo/20210203/7.png" alt="7.png" referrerpolicy="no-referrer"></p> <p>对非常关心延迟指标的金融、证券行业,也在生产环境使用了 APISIX,比如前面提到的<a href="https://www.airwallex.com/cn">空中云汇</a>等。</p> <h2>关于支流科技公司</h2> <p><strong>1. 团队成员</strong></p> <ul> <li>Apache APISIX PMC 成员和 committer</li> <li>Apache Skywalking committer</li> <li>OpenResty(Nginx + Lua)维护者</li> <li>《OpenResty 最佳实践》、《OpenResty 从入门到实践》作者</li> </ul> <p><strong>2. 融资背景</strong></p> <p>支流科技在半年时间内,完成了数百万美元的融资,投资方为<a href="http://www.zhenfund.com/">真格基金</a>和<a href="http://www.zhenchengcap.com/">真成投资</a>。</p> <p><img src="https://static.apiseven.com/logo/20210203/8.png" alt="8.png" referrerpolicy="no-referrer"></p> <h2>云原生下新的技术挑战</h2> <p><strong>1. Kubernetes Ingress controller</strong></p> <p><img src="https://static.apiseven.com/logo/20210203/9.png" alt="9.png" referrerpolicy="no-referrer"></p> <p>APISIX Ingress Controller 基于 APISIX, 集成 Kubernetes 的集群管理能力,支持使用 YAML 申明的方式动态配置入口流量的分发规则、绑定插件,并且支持服务发现、配置校验等能力。APISIX Ingress Controller 将配置写入 APISIX,由 APISIX 承载业务流量。</p> <p>APISIX Ingress Controller 除了覆盖 NGINX Ingress Controller 已有的能力外,还解决了一些 Nginx Ingress Controller 的痛点。目前该项目已捐给 Apache:<a href="https://github.com/apache/apisix-ingress-controller">https://github.com/apache/apisix-ingress-controller</a> 。</p> <table> <thead> <tr> <th>&nbsp;</th> <th><strong>APISIX Ingress Controller</strong></th> </tr> </thead> <tbody> <tr> <td><strong>所属</strong></td> <td>Apache 基金会</td> </tr> <tr> <td><strong>架构</strong></td> <td>基于 Apache APISIX</td> </tr> <tr> <td><strong>动态路由</strong></td> <td>原生支持</td> </tr> <tr> <td><strong>二次开发难度</strong></td> <td>简单,基于 Lua 插件或插件编排(低代码)</td> </tr> <tr> <td><strong>自定义 RPC 协议</strong></td> <td>容易</td> </tr> <tr> <td><strong>性能</strong></td> <td>高</td> </tr> </tbody> </table> <p><strong>2. Service Mesh</strong></p> <p>服务网格是企业内部服务的治理方案。目前通行的方案是 istio + Envoy 的组合。但这个组合也会有很多性能和通用性上问题。</p> <p>支流科技基于 Apache APISIX,给出了更简洁、更通用的服务网格解决方案。</p> <p><img src="https://static.apiseven.com/logo/20210203/10.png" alt="10.png" referrerpolicy="no-referrer"></p> <p><strong>3. 架构演变</strong></p> <p>下图是后端服务架构演变史,APISIX 助力企业完成数字化转型,统一技术栈,降低企业开发、维护成本。开源,不锁定企业用户,建立公开、透明的 IT 市场,让企业用户专注于业务需求自身。</p> <p><img src="https://static.apiseven.com/202102/11.png" alt="11.png" referrerpolicy="no-referrer"></p> <h2>写在最后</h2> <p>伴随着社会数字化的趋势,企业用户数字化、智能化、开放化转型已是大势所趋,行业信息系统的全面转型也是势在必行。“驱动企业数字化转型,协助企业管理并可视化 API 和微服务等关键业务流量,通过大数据和人工智能(AI)加速企业业务决策”,这是支流科技使命。</p>

初探 Kubernetes Service APIs

<h2>前言</h2> <p>我们知道 Kubernetes 为了将集群内部服务暴露出去,有多种方案实现,其中一个比较受大众推崇的就是 Ingress。Ingress 作为一种对外暴露服务的标准,有相当多的第三方实现,每种实现都有各自的技术栈 和 所依赖的网关的影子,相互之间并不兼容。</p> <p>为了统一各种 Ingress 的实现,便于 Kubernetes 上统一管理,<a href="https://github.com/kubernetes/community/tree/master/sig-network">SIG-NETWORK</a> 社区推出了<a href="https://gateway-api.sigs.k8s.io/">Kubernetes Service APIs</a> 一套标准实现,称为第二代 Ingress 。</p> <h2>主题描述</h2> <p>本文从几个问题入手,对 Kubernetes Service APIs 的基本概念进行介绍。</p> <h2>介绍</h2> <h3>Kubernetes Service APIs 号称第二代 Ingress 技术,到底在哪些方面优于第一代?</h3> <p>Kubernetes Service APIs 设计之初,目标并没有局限在 Ingress, 而是为了增强 service networking,着重通过以下几点来增强:表达性、扩展性、RBAC。</p> <ol> <li>更强的表达能力,例如 可以根据 header 、weighting 来管理流量</li> </ol> <pre><code class="language-text">kind: HTTPRoute apiVersion: networking.x-k8s.io/v1alpha1 ... matches: - path: value: "/foo" headers: values: version: "2" - path: value: "/v2/foo" </code></pre> <ol start="2"> <li>增强了扩展能力,Service APIs 提出多层 API 的概念,各层独立暴露接口,方便其他自定义资源与 API 对接,做到更细粒度(API 粒度)的控制。</li> </ol> <p><img src="https://gateway-api.sigs.k8s.io/images/api-model.png" alt="api-model" referrerpolicy="no-referrer"></p> <ol start="3"> <li>面向角色 RBAC:多层 API 的实现,其中一个思想就是从使用者的角度去设计资源对象。这些资源最终会与 Kubernetes 上运行应用程序的常见角色进行映射。</li> </ol> <h2>Kubernetes Service APIs 抽象出了哪些资源对象?</h2> <p>Kubernetes Service APIs 基于使用者角色,将定义了以下几种资源:</p> <p>GatewayClass, Gateway, Route</p> <ol> <li>GatewayClass 定义了一组具有通用配置和行为的网关类型</li> </ol> <ul> <li> <p>与 Gateway 的关系,类似 ingress 中的 ingess.class annotation;</p> </li> <li> <p>GatewayClass 定义了一组共享相同配置和行为的网关。每个 GatewayClass 将由单个 controller 处理,controller 与 GatewayClass 是一对多的关系;</p> </li> <li> <p>GatewayClass 是 cluster 资源。必须至少定义一个 GatewayClass 才能具有功能网关。</p> </li> </ul> <ol start="2"> <li>Gateway 请求一个可以将流量转换为群集内服务的点。</li> </ol> <ul> <li> <p>作用:把集群外的流量引入集群内部。这个才是真正的 ingress 实体;</p> </li> <li> <p>它定义了对特定 LB 配置的请求,该配置也是 GatewayClass 的配置和行为的实现;</p> </li> <li> <p>Gateway 资源可以由操作员直接创建,也可以由处理 GatewayClass 的 controller 创建;</p> </li> <li> <p>Gateway 与 Route 是多对多的关系;</p> </li> </ul> <ol start="3"> <li>Route 描述了通过网关的流量如何映射到服务。</li> </ol> <p><img src="https://gateway-api.sigs.k8s.io/images/schema-uml.svg" alt="schema-uml" referrerpolicy="no-referrer"></p> <p>另外,Kubernetes Service APIs 为了能够灵活的配置后端服务,特地定义了一个 BackendPolicy 资源对象。</p> <p>通过 BackendPolicy 对象,可以配置 TLS、健康检查 以及指定后端服务类型,比如 service 还是 pod。</p> <h2>Kubernetes Service APIs 的推行会带来哪些改变?</h2> <p>Kubernetes Service APIs 作为一种实现标准,带来了以下改变:</p> <ol> <li> <p>通用性: 可以有多种实现,就像 ingress 有多种实现一样,可以根据网关的特点去自定义 ingress controller,但是他们都有一致的配置结构。一种数据结构,可以配置多种 ingress controller。</p> </li> <li> <p>Class 概念:GatewayClasses 可以配置不同负载均衡实现的类型。这些类 class 使用户可以轻松而明确地了解哪些功能可以用作资源模型本身。</p> </li> <li> <p>共享网关:通过允许独立的路由资源 HTTPRoute 绑定到同一个 GatewayClass,它们可以共享负载平衡器和 VIP。按照使用者分层,这使得团队可以安全地共享基础结构,而无需关心下层 Gateway 的具体实现。</p> </li> <li> <p>带类型的后端引用: 使用带类型的后端引用,路由可以引用 Kubernetes Services,也可以引用任何类型的设计为网关后端的 Kubernetes 资源,比如 pod,又或者是 statefulset 比如 DB, 甚至是可访问的集群外部资源。</p> </li> <li> <p>跨命名空间引用:跨不同命名空间的路由可以绑定到 Gateway。允许跨命名空间的互相访问。同时也可以限制某个 Gateway 下的 Route 可以访问的命名空间范围。</p> </li> </ol> <h2>目前有哪些 ingress 实现了 Kubernetes Service APIs ?</h2> <p>目前已知的从代码层面能看到对 Kubernetes Service APIs 资源对象支持的 Ingress 有 Contour, ingress-gce。</p> <h2>Kubernetes Service APIs 如何管理资源读写权限?</h2> <p>Kubernetes Service APIs 按照使用者的维度分为 3 个角色</p> <ol> <li> <p>基础设施提供方 GatewayClass</p> </li> <li> <p>集群操作人员 Gateway</p> </li> <li> <p>应用开发者 Route</p> </li> </ol> <p>RBAC(基于角色的访问控制)是用于 Kubernetes 授权的标准。允许用户配置谁可以对特定范围内的资源执行操作。 RBAC 可用于启用上面定义的每个角色。</p> <p>在大多数情况下,希望所有角色都可以读取所有资源</p> <p>三层模型的写权限如下。</p> <table> <thead> <tr> <th></th> <th>GatewayClass</th> <th>Gateway</th> <th>Route</th> </tr> </thead> <tbody> <tr> <td>Infrastructure Provider</td> <td>Yes</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>Cluster Operators</td> <td>No</td> <td>Yes</td> <td>Yes</td> </tr> <tr> <td>Application Developers</td> <td>No</td> <td>No</td> <td>Yes</td> </tr> </tbody> </table> <h2>Kubernetes Service APIs 有哪些扩展点?</h2> <p>网关的需求非常丰富,同一个场景实现的方式多种多样,各有利弊。Kubernetes Service APIs 提炼出 多层 资源对象,同时也预留了一些扩展点。</p> <p>目前 Kubernetes Service APIs 的扩展点基本集中在 Route 上:</p> <ul> <li> <p>RouteMatch 可以扩展 Route 匹配规则。</p> </li> <li> <p>spcify Backend 可以扩展特定类型的 后端服务, 除了上面提到的 Kubernetes 资源外,还可以扩展比如 文件系统,函数表达式等。</p> </li> <li> <p>Route filter 可以在 Route 的生命周期中增加扩展点,处理 request/response 。</p> </li> <li> <p>Custom Route 以上扩展点都不能满足时,可以完全自定义一个 Route。</p> </li> </ul> <h2>总结</h2> <p>本文通过提问的方式,对 Kubernetes Service APIs 做了一些基本介绍,从整体来看,Kubernetes Service APIs 提炼了很多 ingress 的最佳实践,比如表达能力的增强,其实就是扩展了 Route 的能力,再比如 BackendPolicy 对象,可以为 upstream 指定几乎所有的 Kubernetes 后端资源。当然,项目初期也有不足的地方,目前 Kubernetes Service APIs 虽然已经从大的层面上规定了资源对象,但资源对象内部还有不少细节需要讨论之后再确定,以防止可能出现的冲突场景,结构上存在一定变数。</p>

如何彻底避免正则表达式的灾难性回溯?

<p>正则表达式的灾难性回溯(Catastrophic Backtracking)是指,正则在匹配的时候回溯过多,造成 CPU 100%,正常服务被阻塞。</p> <h2>背景</h2> <p>这里有一篇<a href="https://zhuanlan.zhihu.com/p/38229530">文章</a>详细的描述了一次正则回溯导致 CPU 100% 的发现和解决过程,原文比较长,我之前也在 OpenResty 的开发中遇到过两次类似的问题。</p> <p>这里简单归纳下,你就可以不用花费时间去了解背景了:</p> <ol> <li>大部分开发语言的正则引擎是用基于回溯的 NFA 来实现(而不是基于 Thompson’s NFA);</li> <li>如果回溯次数过多,就会导致灾难性回溯,CPU 100%;</li> <li>需要用 gdb 分析 dump,或者 systemtap 分析线上环境来定位;</li> <li>这种问题很难在代码上线前发现,需要逐个 review 正则表达式;</li> </ol> <p>站在开发的角度,修复完有问题的正则表达式,就告一段落了。最多再加上一个保险机制,限制下回溯的次数,比如在 OpenResty 中这样设置:</p> <pre><code>lua_regex_match_limit 100000; </code></pre> <p>这样即使出现灾难性回溯,也会被限制住,不会跑满 CPU。</p> <p>嗯,看上去已经很完美了吗?让我们来跳出开发的层面,用不同的维度来看待这个问题。</p> <h2>攻击者</h2> <p>只用一台机器,发送一个请求,就可以打跨对方的服务器,这简直就是黑客梦寐以求的核武器。与之相比,什么 DDoS 弱爆了,动静大还花钱多。</p> <p>这种攻击也有自己的名字:<strong>ReDoS (RegEx Denial of Service)。</strong></p> <p>由于正则表达式应用非常广泛,几乎存在于后端服务的各个部分,所以只要找到其中一个漏洞,就有机可趁。</p> <p>试想一个场景,黑客发现了 WAF 中存在 ReDoS 漏洞,发送一个请求打垮了 WAF;你无法在短时间内定位这个问题,<em>甚至意识不到这是一次攻击</em>;为了保证业务的正常,你选择重启或者暂时关闭 WAF;在 WAF 失效期间,黑客利用 SQL 注入,拖走了你的数据库。而你,可能还完全蒙在鼓里。</p> <p>*由于开源软件和云服务的广泛使用,只保证自己写的正则表达式没有漏洞,也是不够的。*这是另外一个话题了,我们这里先只讨论自己可控范围内的正则。</p> <h2>如何发现这类正则表达式?</h2> <p><strong>解决问题的第一步,就是发现问题,而且要尽量发现所有问题</strong>,也就是所谓安全的发现能力。</p> <p>指望人工 code review 来发现有问题的正则,自然是靠不住的。大部分开发者是没有这方面安全意识的,就算有意去找,人也不可能从复杂的正则表达式中找到问题所在。</p> <p>这正是自动化工具大显身手的时候。</p> <p>我们有以下两种自动化的方法来解决:</p> <ul> <li>静态检测</li> </ul> <p>这类工具可以扫描代码中正则表达式,根据一定的算法,从中找出有灾难性回溯的正则。</p> <p>比如 <a href="https://web.archive.org/web/20210225064111/http://www.cs.bham.ac.uk/~hxt/research/rxxr2/">RXXR2</a>,它是基于一篇 paper 中的算法来实现,把正则转换为ε-NFA,然后再进行搜索,但并不支持正则表达式的扩展语法,所以会有漏报。</p> <ul> <li>动态 fuzzing</li> </ul> <p>fuzz 测试是一种通用的软件测试方法,通过长时间输入大量随机的数据,来检测软件是否有崩溃、内存泄漏等问题。</p> <p>同样的,在正则的测试中我们也可以用到这种方法。我们可以根据已有的正则表达式来生成测试数据,也可以完全随机生成。</p> <p>SlowFuzz 是其中一个开源的工具,也是基于一篇 paper 的算法实现,本文最后会列出 paper,它是一个通用的工具,并没有针对正则的结构做处理,所以会有漏报。</p> <p>SDLFuzzer 是几年前微软开发的一个专门的 ReDoS 检测工具,但已经不再维护了。</p> <p>这方面的工具可选择的不多,而且关注度不高。不过让我兴奋的是,在搜索资料的过程中,发现了南京大学几位老师和博士关于 ReDoS 的一篇 paper,并且和 paper 一起开源了 <strong>ReScue</strong> 这个工具:<a href="https://2bdenny.github.io/ReScue/">https://2bdenny.github.io/ReScue/</a>。这个工具已经找出了几个开源项目中的 ReDoS 漏洞。</p> <p>下面是 paper 中对比测试的结果:</p> <p><img src="https://static.apiseven.com/202108/v2-79b82522f7d06f0ae3c4ccc7aa6adc2d_1440w_2.jpg" alt="1.jpg" referrerpolicy="no-referrer"></p> <p><img src="https://static.apiseven.com/202108/v2-37843f350cfce190610379f54a665e00_1440w_2.jpg" alt="2.jpg" referrerpolicy="no-referrer"></p> <h2>可否一劳永逸?</h2> <p>即使我们用了这类工具,有难免会有误报和漏报,那么有没有一劳永逸的方式来解决 ReDoS 呢?</p> <p>那么我们就要回到<strong>问题产生的根源</strong>去寻找答案:<strong>正则引擎使用了回溯的方式来匹配</strong>。</p> <p>如果我们弃用这种方法,是不是就可以了呢?没错,已经有不少其他的正则引擎的实现,都可以一劳永逸的来解决。他们都<strong>放弃了回溯,用 NFA/DFA 自动机的方法来实现</strong>,优点是适合流式匹配,也更加安全,缺点不支持很多正则的扩展语法,比如 backreferences,好在这些一般也用不到。</p> <ul> <li>Google RE2</li> </ul> <p>谷歌的 RE2 是其中完成度比较高开源项目。它支持 PCRE 的大部分语法,而且有 Go、Python、Perl、Node.js 等多种开发语言的库实现,上手和替换成本很低。</p> <p>我们以 Perl 为例,看下 RE2 是否可以避免灾难性回溯问题。</p> <p>我们先来看下这个结果对比图:</p> <p><img src="https://static.apiseven.com/202108/v2-98eb63b55972c76d2257b7814b5a81ab_1440w_2.jpg" alt="3.jpg" referrerpolicy="no-referrer"></p> <p>代码如下,感兴趣的可以自己试试看:</p> <pre><code>time perl -e 'if ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/) {print("hit");}' 40.80s user 0.00s system 99% cpu 40.800 total </code></pre> <p>需要 40.8 秒才能跑完这个正则,期间 CPU 99%。</p> <p>采用 RE2 之后,对比非常明显:</p> <pre><code>time perl -e 'use re::engine::RE2; if ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/) {print(" hit");}' perl -e 0.00s user 0.00s system 34% cpu 0.011 total </code></pre> <ul> <li>Intel Hyperscan</li> </ul> <p>Intel Hyperscan 也是类似 RE2 的正则引擎,也有Perl、Python 等语言的库,上手难度不大。</p> <p>只不过按照 Intel 的惯例,多了平台的绑定,只能跑在 x86 中。</p> <p>如果非要说有什么独特的好处,可能是能够和 Intel 的指令集还有硬件更好的配合,有性能的提升,比如结合下自家的 DPDK。</p> <p>开源的网络入侵检测工具 Snort,也用 Hyperscan 替换了之前的正则引擎,熟悉 Snort 的同学可以试试看。</p> <h2>扩展</h2> <p>这里有几篇正则表达式方面的 paper,感兴趣的可以作为扩展阅读。</p> <p>[1] SlowFuzz: Automated Domain-Independent Detection of Algorithmic Complexity Vulnerabilities: <a href="https://arxiv.org/pdf/1708.08437.pdf">https://arxiv.org/pdf/1708.08437.pdf</a></p> <p>[2] Static Analysis for Regular Expression Exponential Runtime via Substructural Logics:<a href="https://arxiv.org/abs/1405.7058.pdf">https://arxiv.org/abs/1405.7058.pdf</a></p> <p>[3] ReScue: Crafting Regular Expression DoS Attacks:<a href="https://dl.acm.org/doi/10.1145/3238147.3238159">https://dl.acm.org/doi/10.1145/3238147.3238159</a></p> <p>[4] Regular Expression Matching Can Be Simple And Fast: <a href="https://swtch.com/~rsc/regexp/regexp1.html">https://swtch.com/~rsc/regexp/regexp1.html</a></p>

Envoy 与 Apache APISIX: filter 的另一种实现方式

<h2>Envoy filter 现状</h2> <h3>关于 Envoy</h3> <p>Envoy 是由 lyft 开源的可编程边缘和和服务代理,并捐赠给CNCF基金会。在云原生时代,Envoy 被广泛地使用,在服务网格中,Istio、亚马逊 AWS App Mesh 等都使用Envoy作为默认数据面。</p> <h3>Envoy Filter 机制</h3> <p>Envoy 是面向服务架构设计的L7代理和通信总线,核心是一个 L3/L4 网络代理。可插入 Filter 链机制允许开发人员编写 Filter 来执行不同的代理任务并将其插入到主体服务中。</p> <p><img src="https://static.apiseven.com/filters.png" alt="Envoy filter" referrerpolicy="no-referrer"></p> <h3>扩展方式</h3> <p>现有的 Filter 可能无法满足用户的个性化需求,这时候就需要对 Envoy 进行功能扩展,通过在现有的过滤链基础上自定义新的 Filter,实现定制化需求。 用户可以通过以下三种方式对 Envoy 进行扩展:</p> <table> <thead> <tr> <th style="text-align:center"></th> <th style="text-align:center">Getting Started difficulty</th> <th style="text-align:center">stability</th> <th style="text-align:center">development efficiency</th> <th style="text-align:center">Deploy and compile</th> </tr> </thead> <tbody> <tr> <td style="text-align:center">C++</td> <td style="text-align:center">high</td> <td style="text-align:center">stable</td> <td style="text-align:center">low</td> <td style="text-align:center">long time to compile</td> </tr> <tr> <td style="text-align:center">Lua</td> <td style="text-align:center">low</td> <td style="text-align:center">stable</td> <td style="text-align:center">High</td> <td style="text-align:center">no need to compile, deploy directly</td> </tr> <tr> <td style="text-align:center">WASM</td> <td style="text-align:center">high-medium</td> <td style="text-align:center">on the fence</td> <td style="text-align:center">depends on language</td> <td style="text-align:center">compilation time depends on language</td> </tr> </tbody> </table> <ol> <li>编写 C++ 代码扩展</li> </ol> <p>这种方式直接在 Envoy 基础上编写 C++ 代码进行功能增强,实现自定义的 filter 之后,重新编译新的二进制可执行文件,完成现有业务的升级替换。这种方式有以下两方面问题:</p> <ul> <li>受限于 C++ 语言,入门的难度,开发者稀缺。</li> <li>提高了部署、运维、升级的复杂性,Envoy将会变得越来越重,并且每次更改都需要重新编译二进制文件,不利于迭代和管理。</li> </ul> <ol start="2"> <li>使用 lua 脚本扩展</li> </ol> <p>Lua 天生就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能,被广泛应用。</p> <p>HTTP Lua Filter 允许在请求和响应流程中运行 Lua 脚本,目前支持的主要功能包括:对传输的请求或响应流,提供头部、正文和尾部的检查;对头部和尾部进行修改;对上游主机执行异步HTTP调用;直接执行响应并跳过后续的过滤器等。</p> <p>目前很多人配置中直接分发 Lua 代码,不利于代码组织管理,也难以与其他人共享形成生态。</p> <ol start="3"> <li>使用 WASM 扩展</li> </ol> <p>用户可以使用自己擅长的编程语言编写 filter,并使用工具编译成 WASM 格式,嵌入到 Envoy 中运行。</p> <p>它目前支持相对较少的语言,而且使用这些语言来扩展开发仍然不是那么简单。另一方面很多人对 WASM 仍然持保留态度,不会直接投入使用。</p> <h2>Apache APISIX 解决方案</h2> <p><a href="https://github.com/apache/apisix">Apache APISIX</a> 是一个动态、实时、高性能的 API 网关,基于 Nginx 网络库和 Lua 实现, 提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。 基于以上分析,我们可以看到 Lua 是很适合用来扩展 Envoy ,而且它简单易学,开发效率极高,因为嵌入到 Envoy 中,没有额外的网络等开销,性能表现不俗。 Apache APISIX 基于 Lua 提出了自己的解决方案,那就是提供一个强大而灵活的基础库,实现将 Apache APISIX 的所有插件以及未来会开发的插件运行的 Envoy 上,用户也可以基于此基础库开发自己的个性化插件。</p> <h3>示例</h3> <p>具体的代码实现和运行请查看仓库:<a href="https://github.com/api7/envoy-apisix">https://github.com/api7/envoy-apisix</a></p> <p>其中 Envoy 的主要配置如下:</p> <p>定义 Filter</p> <pre><code class="language-yaml">http_filters: - name: entry.lua typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua source_codes: entry.lua: filename: /apisix/entry.lua </code></pre> <p>对路由启用 Filter,并使用 metadata 进行配置</p> <pre><code class="language-yaml">routes: - match: prefix: "/foo" route: cluster: web_service typed_per_filter_config: envoy.filters.http.lua: "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute name: entry.lua metadata: filter_metadata: envoy.filters.http.lua: plugins: - name: uri-blocker conf: rejected_code: 403 block_rules: - root.exe - root.m+ </code></pre> <h3>实现原理</h3> <p>我们无需对 Envoy 进行大幅修改,只需要进行部分优化。</p> <p>我们在插件层屏蔽平台差异,所有需要使用的接口都抽象在了底层框架,我们称之为 apisix.core ,这样所有插件就可以同时运行在 Envoy 和 Apache APISIX 上。</p> <p><img src="https://static.apiseven.com/main.png" alt="Architecture diagram" referrerpolicy="no-referrer"></p> <p>我们以前面的示例来展示整个插件运行过程是怎么样的</p> <p><img src="https://static.apiseven.com/workflow.png" alt="Plugin workflow" referrerpolicy="no-referrer"></p> <p><strong>第一步,读取配置</strong></p> <p>我们通过 metadata 进行配置,决定每个路由上需要运行什么插件,以及插件的配置是什么。</p> <p>示例中,我们对前缀为 /foo 的路由配置了 uri-blocker 插件以及它的 block 规则和需要 block 时响应的 status 。</p> <p><strong>第二步,读取客户端请求数据</strong></p> <p>我们进行了封装,将客户端请求数据统一封装到 ctx 中,以便在整个流程中都可以直接使用。</p> <p><strong>第三步,运行插件</strong></p> <p>我们通过匹配配置的规则和获取到的 uri 来确定是否需要 block 此次请求,如果需要 block ,则调用 respond 直接响应,否则放行。</p> <h2>未来展望</h2> <p>越来越多 APISIX 插件可以运行在 Envoy 上,最后实现在 Envoy 上运行 APISIX 所有及未来开发的所有插件。 同时,我们希望未来能与 Envoy 社区一起,在 Lua Filter 方向发力,优化和完善 Lua Filter ,增强 envoy 扩展能力,降低 Envoy 扩展难度。</p>

如何在 Apache APISIX 上实现插件编排

<div class="iframeBox"> <iframe width="100%" height="100%" src="https://player.bilibili.com/player.html?aid=457628574&amp;bvid=BV145411L7kt&amp;cid=252768596&amp;page=1" scrolling="no" frameborder="no" framespacing="0" allowfullscreen="true" referrerpolicy="no-referrer"></iframe> </div>

基于 Apache APISIX 的全流量 API 网关

<div class="iframeBox"> <iframe src="https://player.bilibili.com/player.html?aid=627161961&amp;bvid=BV1Gt4y1q7qC&amp;cid=237509546&amp;page=1" scrolling="no" frameborder="no" framespacing="0" allowfullscreen="true" style="display: block; width: 100%; height: 100%; padding-top: 30px" referrerpolicy="no-referrer"></iframe> </div>

木兰重生:更多 $ 的妙用,self 的拓展语义

<div class="content"> <p><em><strong>本项目旨在重现「木兰」编程语言的语法和功能。所有例程演示的语法可以用原始的木兰可执行文件</strong></em> <em><strong><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2FMulanRevive%2Fbounty%2Ftree%2Fmaster%2F%2525E5%25258E%25259F%2525E5%2525A7%25258B%2525E8%2525B5%252584%2525E6%252596%252599%2F%2525E5%25258F%2525AF%2525E6%252589%2525A7%2525E8%2525A1%25258C%2525E6%252596%252587%2525E4%2525BB%2525B6" target="_blank">ulang-0.2.2.exe</a></strong></em> <em><strong>检验。如发现有异烦请告知,定将礼谢。</strong></em></p> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F259467288" target="_blank">上篇</a>演示了 $ 在木兰中的部分用途。在开源中国的<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fwww.oschina.net%2Fnews%2F118866%2Fmulan-about-highlight" target="_blank">木兰更新资讯</a>评论区,有几位提出木兰和 PHP 中的 $ 用法有很大不同。</p> <p>这几天基本重现了至今发现的 $ 在木兰中的语法功能,本文逐一介绍。以后有机会再尝试与 PHP 作对比。</p> <h2>木兰的 $ 与 self</h2> <p>木兰编程语言中, $ 的语义是 self。但木兰的 self 与 Python 相比有拓展含义。</p> <p>多数情况下,$ 都可用对应的 self 语法代替。下面先介绍这部分语法。</p> <h2>1 self 可代替</h2> <h3>1.1 类型之外</h3> <p>Python 中的 self 仅在类型中使用,木兰可在类型外使用。self() 返回当前线程(<em><strong>下面带 &gt; 的是在<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F245390062" target="_blank">木兰交互环境</a>下运行的结果</strong></em>)。</p> <div> <pre><code class="language-text">&gt; println(self()) &lt;_MainThread(MainThread, started 140735684096896)&gt; &gt; println(self().ident) 140735684096896 </code></pre> </div> <p>$() 并不允许,因为单独的 $ 不是可识别的标识符。</p> <p>可以如下定义与引用属于当前线程的变量,与全局变量不同:</p> <div> <pre><code class="language-text">&gt; self.x = 1 &gt; self.x 1 &gt; x (..•˘_˘•..) 请先定义'x'再使用位于第1行 </code></pre> </div> <p>对应到 $ 的用法就是:</p> <div> <pre><code class="language-text">&gt; $x = 1 1 &gt; $x 1 </code></pre> </div> <h3>1.2 类型之内</h3> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F259467288" target="_blank">上篇</a>演示的用途,包括类型内变量与方法、单独使用,都可用 self 语法代替,而且 self 的语义与 Python 相同。本周复现了一个不能用 self 代替的语法,见下一部分。</p> <h2>2 self 不可代替</h2> <h3>2.1 可用关键词命名变量</h3> <p>与 PHP 类似,带 $ 可以用关键词命名变量:</p> <div> <pre><code class="language-text">&gt; $while = 4 &gt; $while 4 </code></pre> </div> <p>而用 self 则不允许:</p> <div> <pre><code class="language-text">&gt; self.while = 3 (..•˘_˘•..) File "&lt;STDIN&gt;", line 1:6, unexpected token "WHILE" self.while = 3 ^ &gt; self.while (..•˘_˘•..) File "&lt;STDIN&gt;", line 1:6, unexpected token "WHILE" self.while </code></pre> </div> <h3>2.2 应变属性</h3> <p>应变属性(attr)相关语法对应 Python 中的 @property 和 setter。这里起名“应变属性”是因为 attribute 和 property 的含义区别很微妙,暂时未找到合适中文与类型内一般属性变量作区分,于是用功能特点命名。下图左边为木兰,右边为 Python 同功能代码,重点在三个 attr。这部分功能未发现可代替的 self 语法。</p> <p><img alt="" height="300" src="https://oscimg.oschina.net/oscnet/up-158ba815e39be9d1049b04924f6785c021d.png" width="414" referrerpolicy="no-referrer"></p> <h2>后感</h2> <p>作为未参与木兰原始设计的第三方,仅从复现出的功能很难完全领会设计意图。就个人感觉,木兰的 $ 相关语法设计简化了 Python 的 self 相关语法。对 self 的语义拓展虽然看似信手拈来但也有合理之处。是否对多线程有特别好处待研究。</p> <p>请赐教。</p> <hr> <h3><em><strong>附录:代码量统计</strong></em></h3> <p>主要部分的代码行数统计,格式为:上次-&gt;现在。</p> <ul> <li>木兰代码量 <ul> <li><code>编辑器</code>,包括实现与测试都是木兰代码:184</li> <li>木兰测试用例:2001 -&gt; 2074</li> </ul> </li> <li>Python 代码量(包括测试部分):2338 -&gt; 2379 <ul> <li><code>分析器/语法分析器.py</code>:925 -&gt; 953</li> <li><code>分析器/词法分析器.py</code>:195 -&gt; 198</li> <li><code>测试/运行所有.py</code>,检验所有木兰测试代码片段:185</li> <li><code>环境.py</code>,定义全局方法:150 -&gt; 156</li> <li><code>分析器/语法成分.py</code>,从语法分析器中提取出来的枚举常量:78 -&gt; 79</li> <li>未变 <ul> <li><code>分析器/语法树.py</code>:178</li> <li><code>交互.py</code>,交互环境(REPL):138</li> <li><code>功用/反馈信息.py</code>:49</li> <li><code>中.py</code>,主程序:41</li> <li><code>分析器/错误.py</code>:17</li> <li><code>测试/unittest/语法树.py</code>,确保生成的语法树与原始版本一致:67</li> <li><code>测试/unittest/交互.py</code>,交互环境相关测试:28</li> </ul> </li> </ul> </li> </ul> </div>

木兰重生:$ 的妙用,更多编辑器高亮

<div class="content"> <p><em><strong>所有相关源码在<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2FMulanRevive%2Fmulan-rework" target="_blank">码云</a></strong></em></p> <p>这几天复现了木兰编程语言中 $ 的功能(细节见第二部分)。编辑器的“代码段”类原本(左侧)有 12 处 self,改写后(右侧)看起来就简洁了一些:</p> <p><img alt="" height="464" src="https://oscimg.oschina.net/oscnet/up-16f14e049e830ba0a4e9528c2dcfd81c9e8.png" width="1405" referrerpolicy="no-referrer"></p> <p>后三个函数原本形参只有一个 self,现在变为没有形参,<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F158432288" target="_blank">括号也可以省了</a>。话说这种 $ 的用法也许 PHP 开发者会有特别的感觉?另外,是否要对 $ 打头的标识符进行高亮处理呢?</p> <p>之前 @水木易安 (开源中国)指出了编辑器尚未对内置函数(如 map、print)、类型(如 str、list)进行高亮,于是作了些改进。配色比较随意,望 UX 大佬指点:</p> <p><img alt="" height="316" src="https://oscimg.oschina.net/oscnet/up-4f282fa762f026c5f389545a31e0c7c974c.png" width="696" referrerpolicy="no-referrer"></p> <p>匿名函数中的“-&gt;”,感觉语义和函数定义的“func”相近,于是用了同样高亮风格。</p> <h2>$ 的两种语义</h2> <h3>1. $ 打头的标识符</h3> <p>意为属于当前个体。如开头例程中的“$词性”等价于“self.词性”。</p> <p>如果是函数,第一个形参自动设为 self。如例程中的</p> <div> <pre><code class="language-text">func $开始() {...} </code></pre> </div> <p>等价于</p> <div> <pre><code class="language-text">func 开始(self) {...} </code></pre> </div> <h3>2. 单指 self</h3> <p>与“true/false/nil“类似,可如下使用:</p> <div> <pre><code class="language-java">type 人 { func $起名(名字) { $名字 = 名字; return $ } } println(人().起名("木兰").名字)</code></pre> </div> <h2>代码统计</h2> <p>下面是几个主要部分的代码行数统计,格式为:上次-&gt;现在。</p> <ul> <li>木兰代码量 <ul> <li><code>编辑器</code>,包括实现与测试都是木兰代码:163 -&gt; 184</li> <li>木兰测试用例:1966 -&gt; 2001</li> </ul> </li> </ul> <p>&nbsp;</p> <ul> <li>Python 代码量(包括测试部分):2321 -&gt; 2338 <ul> <li><code>分析器/语法分析器.py</code>:913 -&gt; 925</li> <li><code>分析器/词法分析器.py</code>:190 -&gt; 195</li> <li>未变 <ul> <li><code>分析器/语法树.py</code>:178</li> <li><code>测试/运行所有.py</code>,检验所有木兰测试代码片段:181</li> <li><code>环境.py</code>,加载木兰模块:150</li> <li><code>交互.py</code>,交互环境(REPL):138</li> <li><code>分析器/语法成分.py</code>,从语法分析器中提取出来的枚举常量:78</li> <li><code>功用/反馈信息.py</code>:49</li> <li><code>中.py</code>,主程序:40</li> <li><code>分析器/错误.py</code>:17</li> <li><code>测试/unittest/语法树.py</code>,确保生成的语法树与原始版本一致:67</li> <li><code>测试/unittest/交互.py</code>,交互环境相关测试:28</li> </ul> </li> </ul> </li> </ul> </div>

官方宣布 Perl 7 计划

<div class="content"> <p>Perl 官方<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.perl.com%2Farticle%2Fannouncing-perl-7%2F" target="_blank">宣布了 Perl 7 计划</a>。Perl 7 目前已经在开发中,但是在代码与语法上不会有太大的改变,其本质上是具有现代默认设置的 Perl 5,并为之后进行更大的更改奠定了基础。</p> <p><img height="911" src="https://oscimg.oschina.net/oscnet/up-ec6798b24c9478ac52454f2eaffcda84889.png" width="1141" referrerpolicy="no-referrer"></p> <p>具体来说,新的 Perl 7 实际上约等于<a href="https://www.oschina.net/news/116627/perl-5-32-released">前两天发布的 Perl 5.32</a>。这里边有个有趣的事实是,Perl 6 已经与 Perl “分家”,并且还变成了名为“Raku”的项目。至于分开 v5 与 v6 的原因,相信大家已经不陌生了:Perl 5 与 Perl 6 存在难以调合的兼容性问题,使得二者基本变成为两种不同的语言,而同样名为 Perl,会让人误以为二者只是版本号上有所不同,这会导致损害&nbsp;Perl 的形象。由于“Perl”&nbsp;一词早已普遍被认为是“Perl 5”,因此最终经过讨论,Perl 6 更名了。</p> <p>Perl 7.0 在 v5.32 的基础上会更合理,具有更现代的默认设置,开发者不必管理太多配置,7.0 中已准备好。目前一些实验性功能可能会稳定下来,但没有大的重写或新特性。</p> <p>至于模块库 CPAN(Comprehensive Perl Archive Network),已维护模块应该仍然可以在 7.0 中运行,其余的将会有兼容模式,并且会针对几乎所有 CPAN 模块新版本进行测试。而&nbsp;Perl 7 是否会有单独的 CPAN?这不好说,但是开发人员不想重做可以正常使用的东西,这项变更应该可以通过最少的附带任务进行管理。</p> <p>Perl 7 会减少什么内容呢?不多。默认情况下,某些功能将被禁用,但同样,其本质上是 Perl 5.32,第一轮可能被削的包括:</p> <ul> <li>间接对象​​符号(indirect object notation)</li> <li>裸字文件句柄(bareword filehandles),也许标准文件句柄会除外</li> <li>伪多维数组与哈希(fake multidimensional arrays and hashes),这是旧的 Perl 4 的东西了</li> <li>Perl 4 样式的原型定义(Perl 4-style prototype definitions),使用<code>:prototype()</code>替代</li> </ul> <p>有趣的是,在官方原博客中,谈及为什么从 Perl 5 跳到 Perl 7 而不是 6 的时候(Perl 6 已经改名,那么”Perl 6“这个代号实际上已经空缺出来了),其表示 Perl 曾经雄心勃勃重写 5.8 的计划没了下文……而下一个可用数字为 7,这只是序列上下一个数而已,进行这样的跳跃并非没有先例:</p> <ul> <li>PHP 直接从 5 升级到 7</li> <li>Solaris 2.6 跳到 Solaris 7</li> <li>Java 1.4 跳到&nbsp;Java 5</li> <li>Windows 3.1 跳到 Windows 95(98、ME、2000、XP、Vista、7、8 与 10)</li> <li>……</li> </ul> <p>“至少不是 Perl 34。"</p> <p>最后,Perl 7 有望在一年内发布。</p> </div>

Deno 继颠覆 Node 之后,又“内部”拒绝了 TypeScript

<div class="content"> <p><span style="background-color:#ffffff; color:#4a4a4a">Deno 团队计划删除所有内部代码构建时的 TS 类型检查与捆绑。打算将所有运行时代码转移到同一个 JavaScript 文件当中,但仍将使用随附的 d.ts 文件保存类型定义与说明文档。理由是:</span></p> <ul> <li> <p style="text-align:left">在变更文件时,TypeScript 往往需要几分钟的编译时间,这导致连续编译过程变得非常缓慢;</p> </li> <li> <p style="text-align:left">在创建 Deno 可执行文件以及面向用户的 API 源文件时,TypeScript 结构会引发一系列运行时性能问题;</p> </li> <li> <p style="text-align:left">TypeScript 本身对于 Deno 代码的组织工作毫无帮助,反而增强了代码组织负担。Deno 团队提出的一大现实问题,是 TypeScript 会在两个位置复制相互独立的 Body 类,<span style="color:#888888">https://github.com/denoland/deno/issues/4748</span></p> </li> <li> <p style="text-align:left">由于 TypeScript 编译器无法帮助开发者生成 d.ts 文件,内部代码与运行时 TypeScript 声明必须以手动方式保持同步;</p> </li> <li> <p style="text-align:left">他们维护着两台 TS 编译器主机:一台用于内部 Deno 代码,另一台用于外部用户代码,但二者的作用其实非常相似。</p> </li> </ul> <p><span style="background-color:#ffffff; color:#4a4a4a">需注意的是,<strong>Deno 将仅在内部代码中停用 TypeScript,Deno 用户代码中的 TypeScript 部分仍将保留</strong>,类型检查自然也将并存。</span></p> <p style="text-align:left">虽然 TypeScript 常被视为 JavaScript 的改进版本,但此次情况提醒我们问题也许没那么简单。<strong>与任何其他语言一样,TypeScript 也有自己的缺陷。其最重要的问题之一,在于缓慢的编译速度。</strong> 在从纯 JavaScript 转换至 TypeScript 时,小型项目可能编译变慢的问题还不算严重,但大型项目(例如复杂的 React 应用程序)则将深受其害。从 Deno 项目的体量出发,停止使用 TypeScript 也算是顺理成章。</p> <p style="text-align:left">但这种性能妥协也可以理解,毕竟在开发过程中进行类型检查,相当于用编译时长换取安全保障。当然,TypeScript 项目中也提供关于如何解决并缩短编译时间的大量说明文档。最有趣的方法之一是项目引用,意味着开发人员可以将大规模 TypeScript 代码片段拆分为较小的代码片段。</p> <p style="text-align:left">感兴趣的朋友可以移步 Google 发布的文档,<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fdocs.google.com%2Fdocument%2Fd%2F1_WvwHl7BXUPmoiSeD8G83JmS8ypsTPqed4Btkqkn_-4%2Fpreview%3Fpru%3DAAABcrrKL5k*nQ4LS569NsRRAce2BVanXw" target="_blank">了解 Deno 项目团队在移除 TypeScript 并转而使用 JavaScript 方面的完整讨论</a>。</p> <p style="text-align:justify">Ryan Dahl 及其合作者在其中全面探讨了当前问题、解决方案以及实现途径。</p> <p style="text-align:justify">稿源:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fstartfunction.com%2Fdeno-will-stop-using-typescript%2F" target="_blank">https://startfunction.com/deno-will-stop-using-typescript</a></p> </div>

Go 泛型草案更新,明年8月发布的 Go 1.17 将引入

<div class="content"> <p>Go 团队近日在博客<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fblog.golang.org%2Fgenerics-next-step" target="_blank">介绍</a>了&nbsp;Go 泛型的最新进展。</p> <p><img src="https://static.oschina.net/uploads/space/2020/0618/041434_2pyD_2720166.png" referrerpolicy="no-referrer"></p> <p>Go 团队表示他们一直在完善<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgo.googlesource.com%2Fproposal%2F%2B%2Frefs%2Fheads%2Fmaster%2Fdesign%2Fgo2draft-contracts.md" target="_blank">泛型的设计草案</a>,并为此编写了一个类型检查器——可按照设计草案中的说明,解析使用泛型的 Go 代码并报告任何类型的错误。为了收集社区的反馈,他们还编写了示例代码并在草案中提供。</p> <p>根据收集的反馈和了解的信息,Go 团队发布了<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgo.googlesource.com%2Fproposal%2F%2B%2Frefs%2Fheads%2Fmaster%2Fdesign%2Fgo2draft-type-parameters.md" target="_blank">更新后的泛型设计草案</a>。</p> <p><img src="https://static.oschina.net/uploads/space/2020/0618/075837_Uoas_2720166.png" referrerpolicy="no-referrer"></p> <blockquote> <p><em>"The design is fully backward compatible with Go 1",完全兼容 Go 1</em></p> </blockquote> <p>新版草案最大的变化是放弃了关于&nbsp;contracts 的想法。因为团队认为 contracts 和 interface 类型之间的区别会令人感到困扰,所以他们正在消除这种区别。类型参数现在受 interface 类型约束,当 interface 类型仅作为约束(constraints)使用时,可被允许包含类型列表。在旧版的设计草案中,类型列表属于&nbsp;contracts 的功能。更复杂的情况将使用参数化的 interface 类型.</p> <p>为了帮助决定如何进一步完善设计草案,团队还发布了翻译工具。此工具可用于类型检查,以及运行使用设计草案中描述的泛型版本编写的代码。它通过将泛型代码翻译为普通的 Go 代码来工作。此翻译过程有一定的局限性,不过团队主要是希望借此让大家对 Go 泛型的整体有所了解。如果泛型最终被吸纳,它们的实际实现可能会有所不同。</p> <p>此工具已在 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgo2goplay.golang.org%2F" target="_blank">Go playground 的变体</a>上提供,这个 playground 的工作方式与常见的 Go playground 相同,不过前者支持泛型代码。团队希望此工具能为 Go 用户提供尝试使用泛型的机会,并了解两件主要的事。</p> <p>首先,Go 泛型是否有意义,能给用户带去怎样的惊喜,错误提示消息是否有价值;其次,很多人曾说过需要 Go 泛型,但他们不一定确切知道这意味着什么,那么泛型的设计草案是否以有用的方式解决了此问题。另外,假如有一个问题让人认为“如果 Go 具有泛型,我就可以解决此问题”,那么使用此工具是否可以解决问题?</p> <p>至于具体的推进计划,Go 团队表示要根据从社区收集的反馈而定。如果设计草案受到好评,并且不需要进行重大更改,那么下一步将是<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgolang.org%2Fs%2Fproposal" target="_blank">正式的语言变更提案</a>。</p> <p>为了保证符合预期,如果每个人都对设计草案完全满意,并且不需要进行任何进一步的调整,则最早可以在计划于2021年8月发布的 Go 1.17 中添加泛型。不过可能存在无法预料的问题,所以这是一个乐观的时间表,团队也无法做出任何明确的预测。</p> <p>详情查看&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fblog.golang.org%2Fgenerics-next-step" target="_blank">https://blog.golang.org/generics-next-step</a></p> </div>

EMQ 团队正式向全球 IoT&5G 市场发布开源函数编程语言 - Hamler

<div class="content"> <p><img alt="Hamler - 面向 IoT&amp;5G 市场的开源函数编程语言" height="472" src="https://oscimg.oschina.net/oscnet/up-f1734ea325ca627d79b2950a2bc0365fe36.png" width="1800" referrerpolicy="no-referrer"></p> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.emqx.io%2Fcn%2F" target="_blank">EMQ</a>&nbsp;团队很高兴正式向全球 IoT&amp;5G 市场发布<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fhamler-lang.org%2F" target="_blank">开源函数编程语言 - Hamler</a>!</p> <p>Hamler 是一门构建在<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.erlang.org%2F" target="_blank"> Erlang</a>&nbsp;虚拟机(VM)上的 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.haskell.org%2F" target="_blank">Haskell&nbsp;</a>风格的强类型(strongly-typed)编程语言,独特地结合了编译时的类型检查推导,与对运行时高并发和软实时能力的支持。</p> <p>Hamler 编程语言将赋予行业,尤其是 5G、IoT、云计算和边缘计算等潜力领域,构建下一代高可靠、可扩展、具备软实时支持应用的能力。</p> <h2>为什么发布 Hamler?</h2> <p>近十年来,我们一直在开发基于 Erlang/OTP 的软件系统,特别是我们的核心产品可伸缩分布式<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.emqx.io%2Fcn%2Fproducts%2Fbroker" target="_blank">开源 MQTT 服务器 - EMQ X</a>。我们一直认为 Erlang/OTP,尤其是 Beam 虚拟机是工程学的杰作。它具有出色的并发性、分布性和容错性,是少数正确处理高并发和软实时的通用语言平台,是最适合开发 IoT 和 5G 应用的平台之一。</p> <p>从多年开发 Erlang 程序经验来看,我们一直期待解决两个问题:<strong>编译时类型系统</strong>与<strong>更友好的程序语法</strong>。编译时强大的类型系统有助于我们构建更可靠的软件系统;更友好的语法有助于我们创建一个繁荣的开发者社区。</p> <p>为此学术界和产业界付出了近 20 年的努力。首先是 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPhilip_Wadler" target="_blank">Philip Wadler&nbsp;</a>教授和 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fsimonmar.github.io%2F" target="_blank">Simon Marlow</a>&nbsp;在 2000 年前后,为 Erlang 引入了类型标注和 <strong>Dialyzer</strong>&nbsp;静态类型检查工具。</p> <ul> <li>Simon Marlow &amp; Philip Wadler (1997): <a href="https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fhomepages.inf.ed.ac.uk%2Fwadler%2Fpapers%2Ferlang%2Ferlang.pdf" target="_blank">A practical subtyping system for Erlang</a></li> <li>Philip Wadler (2002): <a href="https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fhomepages.inf.ed.ac.uk%2Fwadler%2Fpapers%2Ferlang%2Ferlang-slides.pdf" target="_blank">The great type hope</a></li> </ul> <p>2008 年后,产业界有近 20 个项目,不断地尝试解决类型系统和友好语法的问题。<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Felixir-lang%2Felixir" target="_blank">elixir&nbsp;</a>项目引入了 Ruby 语法,吸引了部分 Ruby On Rails 社区开发者,却没有类型系统支持。<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fakka.io%2F" target="_blank">Akka</a>&nbsp;项目在 JVM 上模拟实现了 Erlang/OTP ,但丧失了 Erlang/OTP 的软实时特性。<a href="https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fwww.well-typed.com%2F%C2%A0" target="_blank">Well-Typed</a> 公司的 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fhaskell-distributed" target="_blank">Cloud Haskell</a>&nbsp;项目试图在 Haskell 上模拟实现 Erlang/OTP,目前项目已经停滞。此外还有 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Frvirding%2Flfe" target="_blank">lfe&nbsp;</a>引入了 Lisp 语法,<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Falpaca-lang%2Falpaca" target="_blank">alpaca</a>、<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fefene%2Fefene" target="_blank">efene</a>、<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fwende%2Felchemy" target="_blank">elchemy</a>、<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fgleam-lang%2Fgleam" target="_blank">gleam</a>&nbsp;等项目试图引入 ML 风格语法和静态类型,目前大部分仍处于很早期的开发中。</p> <p>今天,EMQ 团队做出努力,采用新的语言架构设计方式再一次尝试解决上述问题,正式向业界发布 Hamler 语言 0.1 版本!</p> <h2>Hamler 语言主要特性</h2> <p>Hamler 作为运行在 Erlang VM 上的类 Haskell 语法的编程语言,核心特性可以概括为:</p> <ul> <li>类 Haskell 和 ML 的友好语法</li> <li>编译时的类型检查与类型推导</li> <li>运行时的高并发、软实时支持</li> </ul> <p>结合我们多年对<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fzh.wikipedia.org%2Fwiki%2F%25E5%2587%25BD%25E6%2595%25B0%25E5%25BC%258F%25E7%25BC%2596%25E7%25A8%258B" target="_blank">函数式编程</a>的理解与开发 Erlang、Haskell 程序的经验,Hamler 语言支持函数编程大部分主要特性,我们相信这些特性可以帮助产业更好地迎接 5G、IoT 、边缘计算与云计算带来的开发浪潮,并吸引更多的开发者使用 Erlang VM - BEAM。</p> <ul> <li>声明式与函数式编程</li> <li>类 Haskell 与 ML 语法</li> <li>编译时类型检查与推导</li> <li>代数类型系统支持(ADT)</li> <li>函数、闭包、高阶函数</li> <li>Currying and partial application</li> <li>Pattern matching, and Guards</li> <li>List comprehension</li> <li>Applicative and Monad</li> <li>更高级的模块系统</li> <li>高并发、软实时支持</li> </ul> <h2>Hamler 编译器设计</h2> <p>Hamler 源码经过词法分析后生成 CST,然后经过 CST -&gt; AST -&gt; CoreFn 的语法树变换、语法分析与类型检查后,生成 CoreErlang 的 IR 代码,然后由 Erlang 编译器生成最终的二进制 Beam 文件。</p> <p>Hamler 编译器架构如下图:</p> <p><img alt="Hamler compiler" height="666" src="https://oscimg.oschina.net/oscnet/up-39e6f0f90fa3d032c7133937c61f3754b9d.png" width="1182" referrerpolicy="no-referrer"></p> <p>Hamler 0.1 编译器最初尝试基于 GHC 8.10.1 实现,后改为基于 Purescript 0.13.6 实现。</p> <h2>欢迎参与 Hamler 开源项目</h2> <p>Hamler 函数编程语言从发起即是一个开源项目,目前核心开发者主要来自 EMQ 公司研发团队:</p> <ul> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Femqplus" target="_blank">Feng Lee</a>: Hamler 语言设计者,贡献了一个梦想和大部分 libs</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2FEMQ-YangM" target="_blank">Yang M</a>: 贡献了 Hamler 编译器大部分代码</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2FSjWho" target="_blank">S Hu</a>: 来自 University of Bristol,贡献了 Hamler 大部分文档</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fwivwiv" target="_blank">wivwiv</a>: 贡献了 hamler-lang.org 网站 theme 设计</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2FCrazyWisdom" target="_blank">CrazyWisdom:</a> 贡献了 hamler-lang.org 网站域名</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fysfscream" target="_blank">ysfscream</a>: 贡献了 hamler-lang.org 网站和 https 设置</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fjuan6666" target="_blank">juan6666</a>:贡献了 Hamler 语言 Logo 设计</li> </ul> <p>Hamler 开源项目最终将与合作伙伴一起,贡献给欧盟 2049 开放源码基金会 - <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2F2049.foundation%2F" target="_blank">2049.Foundation</a>。</p> </div>

Go 语言 2019 调查报告发布

<div class="content"> <p>Go 官方博客昨日公布了<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fblog.golang.org%2Fsurvey2019-results" target="_blank">&nbsp;2019 年 Go 语言调查报告</a>。本次调查收到的回复达到&nbsp;10,975 份,约为<a href="https://www.oschina.net/news/105531/go-survey2018-results">去年</a>的两倍。 这些受访者的反馈意见将被选取用于改进&nbsp;Go 语言的发展。</p> <p>以下是 2019 年度的调查报告摘要:</p> <ul> <li>此调查中,受访者的受众特征与 Stack Overflow 的受访者相似,因此这些结果在某种程度上可以代表更广泛的 Go 开发人员。</li> <li>大多数受访者每天都要用到&nbsp;Go 语言,并且该数字在逐年上升。</li> <li>Go 的使用仍集中在技术公司,但它同时也被用于越来越多的行业,例如金融和媒体。</li> <li>Go 最常见的用途仍然是编写&nbsp;API/RPC 服务和 CLI&nbsp;工具。</li> <li>模块在 Go 生态系统中的使用率很高,与此同时,围绕软件包管理的一些问题仍然存在。</li> <li>有待改进的重点领域包括&nbsp;debug、模块和云服务的体验。</li> <li>VS Code 和 GoLand&nbsp; 依然最受开发者欢迎,有 3/4 的受访者都喜欢它们。</li> </ul> <p>有关调查报告的详细内容请继续往下阅读。</p> <h4>开发者背景</h4> <p>调查结果显示,在工作中使用&nbsp;Go 语言的受访者比例与去年相当,均为 72%,这一数值几乎每年都在增长。在工作之余使用&nbsp;Go 语言的人数比例则有所下降(70%→62%)。</p> <p><img alt="" height="468" src="https://oscimg.oschina.net/oscnet/up-1c31d5dac9cd9493481d2007ec4590f865c.png" width="600" referrerpolicy="no-referrer"></p> <p>从使用年限上来看,56%&nbsp;的受访者使用&nbsp;Go 语言进行开发的经验不足两年,相对来说算是新手。而有着较长时间 Go 开发经验的“老手”,多拥有&nbsp;C/C++&nbsp;背景,对 JavaScript、TypeScript 和 PHP&nbsp;则相对没有那么熟悉。另外,无论是 Go 的新手老手,大多数受访者最熟悉的语言还属 Python。</p> <p><img alt="" height="469" src="https://oscimg.oschina.net/oscnet/up-ed1da08c564d90bfdd3660db491c5fe8047.png" width="600" referrerpolicy="no-referrer"></p> <p>▲ 使用 Go 的时长</p> <p><img alt="" height="418" src="https://oscimg.oschina.net/oscnet/up-ea9b0ac562d5f3b0f2f1e1d31e16301166b.png" width="600" referrerpolicy="no-referrer"></p> <p>▲ 使用其他语言的经验</p> <p>有意思的是,Go 是一个成功的开源项目,但大多数使用它的受访者却“很少”或“从不”为基于 Go 的开源项目做贡献。不过,随着 Go 社区的扩展,为它做贡献的受访者比例在缓慢上升中。</p> <p><img alt="" height="427" src="https://oscimg.oschina.net/oscnet/up-af2a9f2738c6cf81a99afdaa76c34c1a4e3.png" width="600" referrerpolicy="no-referrer"></p> <h4>开发领域</h4> <p>在去年的调查中,多数受访者都集中在技术公司(包括软件、互联网等)。今年的受访者则来自更为广泛的开发领域。尤其是金融行业占比显著增加(8%→12%),来自技术行业的相对受访者比重从 52% 下降至 43%。</p> <p><img alt="" height="486" src="https://oscimg.oschina.net/oscnet/up-e8c86e02a7b621737fbaec2e43a3b58831e.png" width="600" referrerpolicy="no-referrer"></p> <p>具体来讲,在 Go 的使用方面,最常见的领域是 Web 开发(66%)。在数据库相关领域使用 Go 的受访者数量显著增加,所占比例由去年的 29% 上升至 45%,排位也从第五跃升第二。其他常见领域还包括网络编程(42%)、系统编程(38%)和 DevOps(37%)。</p> <p><img alt="" height="425" src="https://oscimg.oschina.net/oscnet/up-74ca3b4afdc9de98f6acb08892467ecdf4a.png" width="600" referrerpolicy="no-referrer"></p> <p>Go 的主要用途依然是编写 API/RPC 服务和开发 CLI 应用程序,这两项分别占比 71% 和 62%。其次是库和框架方面,增长量巨大,所占比例从 30% 飙升至 48%。</p> <p><img alt="" height="557" src="https://oscimg.oschina.net/oscnet/up-5672817b5ff6a0c71a2e9e486c7bbff5a9e.png" width="600" referrerpolicy="no-referrer"></p> <h4>开发环境</h4> <p>与往年一样,绝大多数被调查者表示在 Linux(66%)和 macOS(53%)系统上使用 Go。&nbsp;这是本调查与 StackOverflow 调查存在很大差异的一个地方,后者有 45% 的受访者将 Windows 作为主要开发平台,而关于 Go 的调查中,这一数据只占 20%。</p> <p>另外,受访者中有 38% 的人使用多操作系统应用这门跨平台语言,相较去年(41%)略有下降。</p> <p><img alt="" height="359" src="https://oscimg.oschina.net/oscnet/up-e95625964ae07ddc446e5d596b11e38af7c.png" width="600" referrerpolicy="no-referrer"></p> <p>开发工具方面,VS Code、GoLand 和 Vim 仍占据编辑器排行榜前三位,并且这三位的使用份额占总数据的 3/4。其中&nbsp;GoLand 的使用量在 2019 年增长最多(24%→34%),VS Code 的增长速度有所放缓。</p> <p><img alt="" height="583" src="https://oscimg.oschina.net/oscnet/up-8f20818d19b1e0ba618052b2dbcbcd684bb.png" width="600" referrerpolicy="no-referrer"></p> <p>今年的调查中新增了一个有关内部&nbsp;Go 文档工具的问题。从总体数据来看,少数受访者(6%)表示所在的公司有运行自己的 Go 文档服务器。但如果仅查看大型组织(至少有 5,000 名员工)的数据,这一比例几乎翻了一番(11%)。</p> <p><img alt="" height="195" src="https://oscimg.oschina.net/oscnet/up-6c70eae2d12a6301067d4229ec6cf96b041.png" width="600" referrerpolicy="no-referrer"></p> <h4>云开发</h4> <p>今年的问卷扩展了一些关于云开发的问题,可以看出,选择将 Go 应用部署到云上的开发者越来越多。其中,选择 AWS 的受访者数量(42%)几乎快要追上选择本地部署的受访者数量(44%)。</p> <p>三大全球云提供商(Amazon Web Services、Google Cloud Platform 和 Microsoft Azure)的采用率均呈上升趋势,且牢牢占据绝大部分市场份额。</p> <p>在满意度方面,受访者对在三大云提供商上使用 Go 感到总体满意。AWS 和 GCP 分别以 80% 和 78% 占有最高满意度,而 Azure 的满意度较低,为 57%。</p> <p><img alt="" height="248" src="https://oscimg.oschina.net/oscnet/up-58fa584bc55abed1f87fc95a46686c2f021.png" width="600" referrerpolicy="no-referrer"><br> &nbsp;</p> <h4><strong>对 Go 语言的态度</strong></h4> <p>该问卷包含一个<strong>“你有多大可能将 Go 推荐给朋友或同事?</strong>”的问题,以此来计算<strong>净推荐值</strong>(Net Promoter Score, NPS)。最终 Go 在 2019 年调查中的净推荐值是 60 分(67% 的倡导者 - 7% 的贬低者),去年的调查中这一分数为 61 分。</p> <p><img alt="" height="146" src="https://oscimg.oschina.net/oscnet/up-c8438fd5af2a1d3ab82ac9bca04b2e911e1.png" width="600" referrerpolicy="no-referrer"></p> <p>长期被 Go 使用者诟病的包管理和缺少泛型这两个问题,依然是很多开发者使用 Go 时所面临的最大挑战。今年,提出工具存在问题的受访者比例也有所增加。Go 团队表示这些也是他们重点关注的领域,并表示希望在未来几个月中能够改善开发人员的体验,尤其是在模块、工具和入门经验方面。</p> <p><img alt="" height="476" src="https://oscimg.oschina.net/oscnet/up-5e1b630c0a6f3357a706cb66a1cb433b2b2.png" width="600" referrerpolicy="no-referrer"></p> <h4><strong>Go 语言社区氛围</strong></h4> <p>受访者对于 Go 社区的看法与往年相比有较大波动。认为自己在社区中有受到关注的人数比例从 82% 降至 75%。</p> <p>另一方面,受访者对于这一问题的回应朝着两极分化的方向发展。选择“强烈同意”或“强烈反对”的比例都相对增加。Go 团队计划对此进行进一步研究。</p> <p><img alt="" height="217" src="https://oscimg.oschina.net/oscnet/up-32adb9faf4b004ab1ed9bed6fbaedfef543.png" width="600" referrerpolicy="no-referrer"></p> <h4>最后</h4> <p>以上就是 2019 年度关于 Go 语言调查的大致内容,完整调查报告还请查看 Go 官方博客:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fblog.golang.org%2Fsurvey2019-results" target="_blank">https://blog.golang.org/survey2019-results</a>。</p> </div>

TIOBE 4 月榜单:少儿编程语言 Scratch 进入 TOP 20

<div class="content"> <p>TIOBE <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">已公布</a>&nbsp;2020 年 4&nbsp;月的编程语言排行榜。</p> <p><img src="https://static.oschina.net/uploads/space/2020/0405/073119_7jXU_2720166.png" referrerpolicy="no-referrer"></p> <p>这期的标题主角是 Scratch,它是 MIT Media 实验室&nbsp;Lifelong 幼儿园团队的一个项目,其通过点击并拖拽的方式可视化完成编程,帮助儿童培养编程思维,学会创造性地思考、系统地推理和协同工作。</p> <p><img alt="0c73e2cf2c5fe1c47a519ac40c2314baff8.jpg" src="https://oscimg.oschina.net/oscnet/0c73e2cf2c5fe1c47a519ac40c2314baff8.jpg" referrerpolicy="no-referrer"></p> <p>编程作为现代社会需要的一项技能受到了越来越多的关注,其中就包括儿童这一群体,这期的标题也侧面印证了少儿编程热的流行趋势。</p> <p>可能有人会对 Scratch 居然比不少专业的编程语言更流行觉得不可思议,毕竟这只是一门教孩子入门编程的编程语言。对此 TIOBE CEO 表示,如果考虑到总共有超过 5000 万个项目是用 Scratch “写”出来的,而且每个月都会有 100 万个新的 Scratch 项目加入,这就很难再否认 Scratch 的流行。况且,既然计算机越来越成为生活中不可或缺的一部分,那么教孩子们学习编程的语言流行起来其实也就顺理成章。</p> <p><strong>TIOBE 4&nbsp;月 TOP 20 编程语言</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0405/081618_KbMX_2720166.png" referrerpolicy="no-referrer"></p> <p>可以看到,除了 Scratch,SQL、Go、Swift 和 R 语言在这个月都有着激烈的上升趋势,R 语言更是进入了 TOP 10。</p> <p>上个月被认为呈现<a href="https://www.oschina.net/news/113844/tiobe-index-202003" target="_blank">“没落”</a>迹象的 Delphi,在这次的榜单中跌出了前 20。缺少苹果扶持的 Objective-C 虽然一直在 TOP 20 内徘徊,但它显示出的却是下降趋势。</p> <p><strong>TOP 10 编程语言 TIOBE 指数走势(2002-2020)</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0405/082607_BwjR_2720166.png" referrerpolicy="no-referrer"></p> <p><strong>第 21-50 名的编程语言排行</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0405/082723_xJZQ_2720166.png" referrerpolicy="no-referrer"></p> <p>第 51-100 名如下,由于它们之间的数值差异较小,仅以文本形式列出(按字母排序):</p> <blockquote> <p>(Visual) FoxPro, ABC, ActionScript, Alice, Arc, ATLAS, Awk, bc, Bourne shell, C shell, CL (OS/400), Clojure, Common Lisp, Crystal, cT, Elixir, Forth, Hack, Icon, Inform, Io, J, Korn shell, Ladder Logic, LiveCode, Maple, Mercury, MQL4, NATURAL, Object Pascal, OCaml, OpenCL, OpenEdge ABL, Oz, PL/I, PostScript, Programming Without Coding Technology, Pure Data, Q, Red, Ring, S, Smalltalk, Solidity, SPARK, Tcl, Vala/Genie, Verilog, VHDL, Whitespace</p> </blockquote> <p>TIOBE 编程社区指数(The TIOBE Programming Community index)是编程语言流行度的指标,该榜单每月更新一次,指数基于全球技术工程师、课程和第三方供应商的数量。包括流行的搜索引擎,如谷歌、必应、雅虎、维基百科、亚马逊、YouTube 和百度都用于指数计算。具体的计算方式见这里:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2Fprogramming-languages-definition%2F" target="_blank">https://www.tiobe.com/tiobe-index/programming-languages-definition/</a>。</p> <p>值得注意的是,TIOBE 指数并不代表语言的好坏,开发者可以使用该榜单检查自身的编程技能是否需要更新,或者在开始构建新软件时对某一语言做出选择。</p> <p>详细榜单信息可以查看<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">&nbsp;TIOBE 官网</a>。</p> </div>

2020 年第一个候选 Java 增强提案,删除 Nashorn JavaScript 引擎

<div class="content"> <p>Oracle 软件研发总监 Jim Laskey 提出了一项候选 Java 增强提案(JEP),要删除长期以来一直使用的&nbsp;Nashorn JavaScript 引擎、相关 API 和<code>jjs</code>工具。这是 2020 年第一个进入候选名单的 JEP,并且比较成熟,有望在 JDK 15 中实施。</p> <p><img height="471" src="https://static.oschina.net/uploads/space/2020/0228/082319_bSd0_3820517.png" width="700" referrerpolicy="no-referrer"></p> <p>编号 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fopenjdk.java.net%2Fjeps%2F372" target="_blank">JEP 372</a>,该提案表示:Nashorn JavaScript 引擎最初通过&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fopenjdk.java.net%2Fjeps%2F174" target="_blank">JEP 174</a>&nbsp;集成到 JDK 8 中,用以替代 Rhino 脚本引擎。当时它是 ECMAScript-262 5.1 标准的完整实现。但随着 ECMAScript 语言构造以及 API 的快速适应和修改,我们发现 Nashorn 难以维护。</p> <p>根据该提议,两个 JDK 模块将被永久删除:</p> <ul> <li> <p><code>jdk.scripting.nashorn</code>:包含&nbsp;<code>jdk.nashorn.api.scripting</code>&nbsp;与&nbsp;<code>jdk.nashorn.api.tree</code>&nbsp;包</p> </li> <li> <p><code>jdk.scripting.nashorn.shell</code>:包含&nbsp;<code>jjs</code>&nbsp;工具</p> </li> </ul> <p>但这一弃用将不会以任何方式影响 javax.script API。</p> <p>Nashorn JavaScript 引擎发布时,其性能与之前的 Rhino 实现相比,提升达到 2 到 10 倍,这也是它能替代前者的原因之一,并且其采用也很广泛。但是在&nbsp;2018 年 9 月发布的 JDK 11 中已经将其弃用(<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fopenjdk.java.net%2Fjeps%2F335" target="_blank">JEP 335</a>),JEP 372 认为这么长的时间过去了,使用它的开发人员已经有足够的时间进行了迁移。</p> <p>不过开发者对此有不同看法,有人认为 Java 一直以高度向后兼容闻名,不应该删除,有人吐槽公司还在使用 Rhino,也有人建议直接<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fjava%2Fcomments%2Ffa3v82%2Fjep_372_remove_the_nashorn_javascript_engine%2F" target="_blank">切换到 GraalVM</a>,因为它是 JavaScript 与&nbsp;Node 的更完整的实现,并且速度更快。</p> </div>

Golang 之禅

<div class="content"> <p>在本月初的 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.gophercon.org.il%2F" target="_blank">GopherCon</a>&nbsp;上,知名 Go 语言贡献者与布道师&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fdave.cheney.net%2Fabout" target="_blank">Dave Cheney</a>&nbsp;发表了名为《The Zen of Go》的演讲,之后他整理了演讲内容在博客中分享,由于内容过长,他又写了一个简洁版本:</p> <ul> <li>完整版:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fdave.cheney.net%2F2020%2F02%2F23%2Fthe-zen-of-go" target="_blank">https://dave.cheney.net/2020/02/23/the-zen-of-go</a></li> <li>简洁版:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fthe-zen-of-go.netlify.com%2F" target="_blank">https://the-zen-of-go.netlify.com</a></li> </ul> <p>这里简单翻译一下简洁版本的内容:</p> <p>编写简单、可读、可维护的 Go 代码的十个工程要点。</p> <p><strong>每个包实现单一目标</strong></p> <p>设计良好的 Go 软件包提供一个单一的思路,以及一系列相关的行为。一个好的 Go 软件包首先需要选择一个好名字,使用电梯法则(30 秒内向客户讲清楚一个方案),仅用一个词来思考你的软件包要提供什么功能。</p> <p><strong>明确处理错误</strong></p> <p>健壮的程序其实是由处理故障案例的片段组成的,并且需要在故障出现之前处理好。冗余的<code>if err != nil { return err }</code>比出了故障再一个个去处理更有价值。panic 和&nbsp;recover 也一样。</p> <p><strong>尽早 return,不要深陷</strong></p> <p>每次缩进时都会在程序员的堆栈中添加另一个先决条件,这会占用他们短期内存中的 7±2 个片段。避免需要深层缩进的控制流。与其深入嵌套,不如使用守卫子句将成功路径保持在左侧。</p> <p><strong>并发权留给调用者</strong></p> <p>让调用者选择是否要异步运行你的库或函数,不要强制他们使用异步。</p> <p><strong>在启动 goroutine 之前,要知道它什么时候会停止</strong></p> <p>goroutines 拥有资源、锁、变量与内存等,释放这些资源的可靠方法是停止&nbsp;goroutine。</p> <p><strong>避免包级别的状态</strong></p> <p>要完成明确和减少耦合的操作,需要通过提供类型需要的依赖项作为该类型上的字段,而不是使用包变量。</p> <p><strong>简单性很重要</strong></p> <p>简单性不是老练的代名词。简单并不意味着粗糙,它意味着可读性和可维护性。如果可以选择,请遵循较简单的解决方案。</p> <p><strong>编写测试以确认包 API 的行为</strong></p> <p>软件包的 API 是与使用者的一份合约,不管先后,不管多少,一定要进行测试。测试是确定合约的保证。要确保测试使用者可以观察和依赖的行为。</p> <p><strong>如果你认为速度缓慢,先通过基准测试进行验证</strong></p> <p>以性能之名会犯下许多危害可维护性的罪行。优化会破坏抽象、暴露内部和紧密耦合。如果要付出这样的代价,请确保有充分理由这样做。</p> <p><strong>节制是一种美德</strong></p> <p>适度使用 goroutine、通道、锁、接口与嵌套。</p> </div>

C++ 20 准备发布,C++ 23 提上议程

<div class="content"> <p>根据 Reddit C++ 版块上的信息,C++ 20 已经正式通过委员会草案,这意味着 C++ 20 可以准备发布。</p> <p><img height="263" src="https://oscimg.oschina.net/oscnet/up-201ca6bf0a828de00d31894ddd7ba9ec31f.png" width="700" referrerpolicy="no-referrer"></p> <p>“在近期 ISO C++ 委员会会议上,我们完成了 C++ 20 委员会草案,并投票决定将国际标准草案(DIS,Draft International Standard)发送出去,以进行最终批准和发布”,帖子介绍:“在程序上,DIS 可能会被拒绝,但是由于我们的程序和过程,这种情况极不可能发生。这意味着 C++ 20 已经完成,并将在几个月后发布该标准。”</p> <p>C++ 20 是近十年来影响最大的一个版本,新的特性众多,包括:</p> <ul> <li>模组(Modules)</li> <li>协程(Coroutines)</li> <li>标准库 Concepts 的概念</li> <li>范围(range)</li> <li><code>constexpr</code>支持:<code>new</code>/&nbsp;<code>delete</code>、<code>dynamic_cast</code>、<code>try</code>/&nbsp;<code>catch</code>、虚拟</li> <li><code>constexpr</code>&nbsp;向量和字符串</li> <li>计时:日历、时区支持</li> <li><code>std::format</code></li> <li><code>std::span</code></li> <li><code>std::jthread</code></li> </ul> <p>预计这些新特性将会对开发者及 C++ 生态产生不小影响,特别是像协程这种巨大的变化。</p> <p>同时,在此次会议期间,委员会还通过了 C++ 23 计划,其中包括优先考虑模块化标准库、对协程、执行程序和网络的库支持。</p> <p>更加具体的内容可以查看原帖:</p> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fcpp%2Fcomments%2Ff47x4o%2F202002_prague_iso_c_committee_trip_report_c20_is%2F" target="_blank">https://www.reddit.com/r/cpp/comments/f47x4o/202002_prague_iso_c_committee_trip_report_c20_is</a></p> </div>

API 网关 Apache APISIX 和 Kong 的选型对比

<p>Apache APISIX 和 Kong 都是开源的微服务 API 网关,那么在这两者之间,如何去做比较和选择呢?</p> <p>这两个项目都有完善的文档和测试来覆盖,也有不少的生产用户在使用,所以不用去担心稳定性和它们的可持续发展,本文会从功能和性能这两个最直接和可验证的角度去做下对比。</p> <h3>功能</h3> <p>从 API 网关核心功能点来看,两者均已覆盖:</p> <table> <thead> <tr> <th style="text-align:left"><strong>功能</strong></th> <th style="text-align:left"><strong>Apache APISIX</strong></th> <th style="text-align:left"><strong>KONG</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:left"><strong>动态上游</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>动态路由</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>健康检查和熔断器</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>动态 SSL 证书</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>七层和四层代理</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>分布式追踪</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>自定义插件</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>REST API</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> <tr> <td style="text-align:left"><strong>CLI</strong></td> <td style="text-align:left">支持</td> <td style="text-align:left">支持</td> </tr> </tbody> </table> <p>更详细的比较:</p> <table> <thead> <tr> <th style="text-align:left"><strong>功能</strong></th> <th style="text-align:left"><strong>Apache APISIX</strong></th> <th style="text-align:left"><strong>KONG</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:left">项目归属</td> <td style="text-align:left">Apache 软件基金会</td> <td style="text-align:left">Kong Inc.</td> </tr> <tr> <td style="text-align:left">技术架构</td> <td style="text-align:left">Nginx + etcd</td> <td style="text-align:left">Nginx + postgres</td> </tr> <tr> <td style="text-align:left">交流渠道</td> <td style="text-align:left">微信群、QQ 群、邮件列表、GitHub、meetup</td> <td style="text-align:left">GitHub、论坛、freenode</td> </tr> <tr> <td style="text-align:left">单核 QPS (开启限流和 prometheus 插件)</td> <td style="text-align:left">18000</td> <td style="text-align:left">1700</td> </tr> <tr> <td style="text-align:left">平均延迟</td> <td style="text-align:left">0.2 毫秒</td> <td style="text-align:left">2 毫秒</td> </tr> <tr> <td style="text-align:left">支持 Dubbo 代理</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">配置回滚</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">支持生命周期的路由</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">插件热更新</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">用户自定义:负载均衡算法、路由</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">resty &lt;--&gt; gRPC 转码</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">支持 Tengine 作为运行时</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">MQTT 协议支持</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">配置生效时间</td> <td style="text-align:left">事件通知,低于 1 毫秒更新</td> <td style="text-align:left">定期轮询,5 秒</td> </tr> <tr> <td style="text-align:left">自带控制台</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">对接外部身份认证服务</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">配置中心高可用(HA)</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">指定时间窗口的限速</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> <tr> <td style="text-align:left">支持任何 Nginx 变量做路由条件</td> <td style="text-align:left">是</td> <td style="text-align:left">否</td> </tr> </tbody> </table> <h3>性能</h3> <p>在开启了 limit-count 和 prometheus 这两个插件后,Apache APISIX 的性能是 Kong 的十倍, 这里有比较详细的<a href="https://gist.github.com/membphis/137db97a4bf64d3653aa42f3e016bd01">步骤</a>, 感兴趣的开发者可以按图索骥,来进行验证。</p> <h3>原因</h3> <p>这里,我们讨论下功能和性能差异背后的原因:</p> <ol> <li> <p>Apache APISIX 的路由复杂度是 O(k),只和 uri 的长度有关,和路由数量无关;kong 的路由时间复杂度是 O(n),随着路由数量线性增长。</p> </li> <li> <p>Apache APISIX 的 IP 匹配时间复杂度是 O(1),不会随着大量 IP 判断而导致 CPU 资源跑满。</p> </li> <li> <p>Apache APISIX 的路由匹配,接受 Nginx 的所有变量作为条件,并且支持自定义函数;其他网关都是内置的几个条件。</p> </li> <li> <p>Apache APISIX 使用 etcd 作为配置中心,没有单点,任意宕掉一台机器,网关集群还能正常运行。其他基于关系型数据库的网关都会有单点问题。</p> </li> <li> <p>Apache APISIX 的配置下发只要 1 毫秒就能达到所有网关节点,使用的是 etcd 的 watch;其他网关是定期轮询数据库,一般需要 5 秒才能获取到最新配置。</p> </li> <li> <p>Apache APISIX 的插件都经过精心调优,在高压力下依然保持毫秒级别延迟。</p> </li> <li> <p>Apache APISIX 独有的插件编排和低代码功能,可以大大降低二次开发的门槛。</p> </li> </ol> <div class="iframeBox"> <iframe src="https://player.bilibili.com/player.html?aid=626036919&amp;bvid=BV1Zt4y1X73K&amp;cid=202170017&amp;page=1" frameborder="0" scrolling="no" style="display: block; min-width: 100%; width: 100%; height: 100%; border: none; overflow: auto;" referrerpolicy="no-referrer"></iframe> </div>

TIOBE 1 月榜单:C 获得“2019 年度编程语言”称号

<div class="content"> <p>TIOBE&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">公布</a>了 2020 年最新一期的编程语言排行榜。</p> <p><img src="https://static.oschina.net/uploads/space/2020/0107/083905_FCY5_2720166.png" referrerpolicy="no-referrer"></p> <p><strong>TOP 20 的榜单如下:</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0107/083950_Va49_2720166.png" referrerpolicy="no-referrer"></p> <p><img src="https://static.oschina.net/uploads/space/2020/0107/084028_QWBd_2720166.png" referrerpolicy="no-referrer"></p> <p>上个月我们<a href="https://www.oschina.net/news/111968/tiobe-index-201912" target="_blank">预测</a> Java, C, Python 和 C# 将有机会获得“年度编程语言”称号,现在结果已公布,相信出乎很多人意料 —— 古老的 C 语言了荣获 2019 年度编程语言称号。</p> <p>不少人认为 Python 会连续两年成为 TIOBE 的年度编程语言,毕竟它的火热程度在近几年是有目共睹。然而这一次偏偏是古老的 C 语言凭借 2.4% 的年增长率获得了此称号。排名第二的是&nbsp;C# (+2.1%),然后分别是 Python (+1.4%) 和 Swift (+0.6%)。</p> <p>给人感觉十分低调的 C 语言居然是如此的热门,原因为何?TIOBE 认为这种趋势背后的主要驱动力是<strong>物联网(IoT)和当今发布的大量小型智能设备</strong>。当将 C 应用于对性能至关重要的小型设备时,它的表现会十分出色。另外,C 语言也比较容易学习,并且每个处理器都有一个 C 编译器。</p> <p>其他一些表现不错令人惊喜的编程语言:Swift(从第 15 名上升至第 9 名)和 Ruby(从第 18 名上升至第 11 名)。Swift 也已是稳定排名前十的编程语言,而 Ruby 也似乎很快就会进入 TOP 10。</p> <p>当然还有一些潜力巨大但在 2019 年却没获得突破的编程语言:Rust 仅前进了 3 名(从第 33 名上升到第 30 名);Kotlin 则掉了 3 个名次(从第 31 名跌到第 35 名);Julia 甚至失去 10 个名次(从 37 名跌到&nbsp;47 名),口碑甚好的 TypeScript 也仅仅是前进了一名(从第 49 上升至第 48)。</p> <p><strong>TOP 10 编程语言 TIOBE 指数走势(2002-2020)</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0107/090255_VY25_2720166.png" referrerpolicy="no-referrer"></p> <p>继续看看 20 名后的排位:</p> <p><strong>第 21-50 名的编程语言排行</strong></p> <p><img src="https://static.oschina.net/uploads/space/2020/0107/090419_mggc_2720166.png" referrerpolicy="no-referrer"></p> <p>第 51-100 名如下,由于它们之间的数值差异较小,仅以文本形式列出(按字母排序):</p> <blockquote> <p>(Visual) FoxPro, 4th Dimension/4D, ABC, Alice, Apex, Awk, Bash, bc, Bourne shell, C++/CLI, CL (OS/400), Clojure, CoffeeScript, Common Lisp, Crystal, cT, Elixir, Emacs Lisp, Erlang, Forth, Hack, Icon, Inform, Io, Korn shell, Ladder Logic, Limbo, Maple, Mercury, MQL4, NATURAL, OpenCL, Oz, PostScript, PowerShell, Programming Without Coding Technology, Pure Data, Q, Raku, Red, Ring, S, Smalltalk, SPARK, Standard ML, VBScript, VHDL, WebAssembly, XBase++, Z shell</p> </blockquote> <p>TIOBE 编程社区指数(The TIOBE Programming Community index)是编程语言流行度的指标,该榜单每月更新一次,指数基于全球技术工程师、课程和第三方供应商的数量。包括流行的搜索引擎,如谷歌、必应、雅虎、维基百科、亚马逊、YouTube 和百度都用于指数计算。具体的计算方式见这里:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2Fprogramming-languages-definition%2F" target="_blank">https://www.tiobe.com/tiobe-index/programming-languages-definition/</a>。</p> <p>值得注意的是,TIOBE 指数并不代表语言的好坏,开发者可以使用该榜单检查自身的编程技能是否需要更新,或者在开始构建新软件时对某一语言做出选择。</p> <p>详细榜单信息可以查看<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">&nbsp;TIOBE 官网</a>。</p> </div>

2019 年 C++ 的发展

<div class="content"> <p>C++ 软件工程师&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.bfilipek.com%2Fp%2Fabout.html" target="_blank">Bartek</a> 发布博客,回顾了 C++ 2019 年的发展情况。</p> <p><img height="247" src="https://oscimg.oschina.net/oscnet/up-c15ef3c1d6aabee537a57e778c037089f56.png" width="700" referrerpolicy="no-referrer"></p> <p>Bartek 主要介绍了 2019 年 C++ 特性上的演进、在全球的会议重大进展、相关工具的改进,以及 C++20 新特性的期待,他总结成三大关注点:</p> <ul> <li>C++20 即将发布</li> <li>工具,现在编写 C++ 代码更加容易</li> <li>C++ 在开发者中的稳定地位</li> </ul> <p>(需要注意的是,文章提出的只是 Bartek 的观点,并不代表整个 ISO C++ 委员会的观点。)</p> <p>从下表可以看出来,在 2019 年,C++ 社区做了许多事情,像我们关注的新特性方面的进展也有不少,比如 Clang <a href="https://www.oschina.net/news/105330/llvm-8-0-0-released">8.0.0</a>、<a href="https://www.oschina.net/news/109997/llvm-9-0-released">9.0.0</a> 发布、<a href="https://www.oschina.net/news/105629/vs2019-general-availability">VS 2019</a> 发布与 <a href="https://www.oschina.net/news/106414/gcc-9-1-released">GCC 9.1</a>&nbsp;发布等。</p> <p><img height="522" src="https://oscimg.oschina.net/oscnet/up-716f022528d28a5cd0e78b5b15486c27682.png" width="400" referrerpolicy="no-referrer"></p> <p>简单总结起来,从 Clang 5.0、GCC 8.0、Visual Studio 15.7 和 Intel C++ Compiler 19.0.1 开始,所有编译器都支持 C++ 语言特性。虽然&nbsp;C++&nbsp;标准库需要其它支持,特别是并行算法和文件系统之类的要点。<code>std::filesystem</code>需要使用到 GCC 8.1(或更高版本的 GCC 9.1)、Clang 3.9(或更高版本 7.0)和 Visual Studio 2017 15.7。并行算法更新最早在 Visual Studio 2017 15.7 中可用,但随着之后的更新,MSVC 团队添加了更多算法。</p> <p>关于 C++20 的讨论在今年占据主导地位,目前特性已经冻结,主要特性包括:</p> <ul> <li>模组</li> <li>协程</li> <li>标准库 Concepts 的概念</li> <li>范围</li> <li><code>constexpr</code>支持:<code>new</code>/&nbsp;<code>delete</code>,<code>dynamic_cast</code>,<code>try</code>/&nbsp;<code>catch</code>,虚拟</li> <li><code>constexpr</code>&nbsp;向量和字符串</li> <li>计时:日历、时区支持</li> <li><code>std::format</code></li> <li><code>std::span</code></li> <li><code>std::jthread</code></li> </ul> <p>目前流行的编译器已经实现了许多 C++20 特性。Bartek 列了一个 C++20 新特性的小表:</p> <p><img height="269" src="https://oscimg.oschina.net/oscnet/up-e4226347eeb58c27a614c3612ec36529947.png" width="500" referrerpolicy="no-referrer"></p> <p>工具方面,首先 Visual Studio 对 C++ 提供了众多新的支持,包括:</p> <ul> <li>使用 Visual Studio IntelliCode 的 AI 辅助代码补全</li> <li>C++ MSBuild 和 CMake 项目中对 Clang 和 MSVC 的 Clang-Tidy 的支持</li> <li>AddressSanitizer 支持 Windows 上使用 MSVC 编译的项目</li> <li>与 CMake 更好地集成</li> <li>Concepts&nbsp;在 16.3 中可用</li> <li>全面的<code>to_chars()</code>支持,满足了 C++ 17 的要求</li> <li>提供了一个名为 C++ Build Insights 的新工具集合。</li> </ul> <p>Clang/LLVM 提供了许多强大的实用程序,比如:</p> <ul> <li>Clang Tidy</li> <li>Clang-Include-Fixer</li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fclang.llvm.org%2Fdocs%2FAddressSanitizer.html" target="_blank">AddressSanitizer</a></li> <li><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fclang.llvm.org%2Fdocs%2FMemorySanitizer.html" target="_blank">MemorySanitizer</a></li> </ul> <p>代码分析器 CppDepend v2019.3 增加了对 MISRA 编码准则的支持,VS 2019 支持、QT 项目、简化的 UI、嵌入式项目支持。</p> <p>PVS-Studio,这是一个使用 C/C++ 和 C# 编写的程序源代码中的错误检测工具,2019 年的一些显着变化包括 .NET Core 3 和 3.1 项目分析、MISRA 准则支持、40 多项新检查、对 Java、SonarQube 插件的支持。</p> <p>包管理器&nbsp;Conan&nbsp;与&nbsp;Microsoft/vcpkg 也都有一些新变化。</p> <p>最后 Bartek 介绍了 2019 年 C++ 在开发者人群中的地位依然稳定:</p> <ul> <li>TIOBE 编程语言排行榜中&nbsp;C++ 排到了第 4 位。</li> <li>在 StackOverflow 调查中,C++ 位于 C 上方的第 9 位。</li> <li>在 GitHub 的调查中,C++ 排名第 6。</li> </ul> <p>除了这些榜单,Bartek 自己也做了一个简单的调查问卷,结果显示了关于 C++ 的使用情况,包括使用的标准库版本、工具的选择与对 C++20 新特性的使用等:</p> <p><img height="665" src="https://oscimg.oschina.net/oscnet/up-0c427072a80149c365a35a2cff94329223f.png" width="600" referrerpolicy="no-referrer"></p> <p>完整回顾内容查看原博客:</p> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.bfilipek.com%2F2019%2F12%2Fcpp-status-2019.html" target="_blank">https://www.bfilipek.com/2019/12/cpp-status-2019.html</a></p> </div>

从 0 到 1:Apache APISIX 的 Apache 之路

<p>Apache APISIX 是一个很年轻的项目,2019 年 6 月份开源,7 月份加入到 CNCF 全景图,10 月份进入 Apache 孵化器,所以我会和大家分享一下 APISIX 是如何从 0 到 1,进入 Apache 孵化器的。</p> <p>Apache APISIX 现在有 17 个 committer,分别来自 16 家不同的公司,是一个非常社区化的项目。每个 committer 有一票,决定版本的发布、选举新的 committer 和 PPMC 等比较重大的事情。</p> <h3><strong>Apache Way</strong></h3> <p>Apache Way 是大家都比较熟悉的概念:社区大于代码。烂代码可以改,Apache 的导师并不会指导你怎么写出更高水平的代码,他们更关心的是社区是否在健康的发展,只要有一个好的社区,烂代码一定会有更高水平的人进行重构,变成更好的代码。所以只要社区在,那这个项目就能够一直存活下去,这在 Apache 是最重要的。</p> <p>邮件列表优先是另一个重要的点,没有在邮件列表中出现过的,就当做不存在。这在中国其实是一个非常大的挑战,大家在文化上和习惯上都不太喜欢用邮件:第一是时间不够及时,可能发一封邮件后隔 1-2 天才能收到回复;第二是邮件列表的很多东西是公开的,有些人喜欢私聊;第三邮件列表里面只能出现英文,但其实中国人的英文是不差的,我们比大部分其他国家的人的英语已经好很多了,毕竟我们学了很多年英语,还有各种翻译的软件,即使出现语法错误,都不是大问题。</p> <p>第三是精英治理,在 Apache 社区中大家用贡献来获得更多的话语权,更高的 title 意味着更多的付出和责任。</p> <p>第四是民主,所有人都可以参与 Apache 的投票,即使你不在 Apache 社区,但不是所有人的票都是有效的。比如 APISIX 当时进 Apache 孵化器的时候需要投票,我也可以投支持票,但后面要写 no binding,表示我支持并关注这个项目,但我是一个观察员的身份,但这一票对于项目能否进 Apache 是没有决定性的。只有对孵化器做了贡献并得到社区认可的人,即 Apache 孵化器的 PMC,他们的票才是有效的,这就是 Apache 社区的民主。</p> <h3><strong>开源社区的治理模式</strong></h3> <p>我们知道很多开源项目有一些是在基金会下面,有一些则不是。在基金会下面的项目,比如 Linux 基金会、Apache 基金会,他们的治理模式叫做“社区共识”,需要先在社区内讨论,在达成一个共识后进行投票,而非直接投票。如果直接投票,没有前面的讨论和共识,那这个投票是没有意义的。这个效率可能会很慢,但只有整个社区达成共识,后面才没有异议。</p> <p>第二种是商业公司共识,只要商业公司的人达成共识,那么这个 PR 或者 feature 就可以被合并,而社区的人说话是没有用的,因为它是被商业公司控制的一个项目,而如果你给商业公司贡献代码,能否被合并决定权在商业公司手里。这对于个人开发者可能无所谓,但是对于参与项目的企业则是有所谓的,通常企业会有私有版本,企业想把自己的 feature 贡献回社区,但商业公司可能因为这个东西和自己的商业版本冲突,就会拒绝掉。</p> <p>第三种是仁慈的独裁者,最典型的就是 Python,个人决定着开源项目的发展。</p> <p>上述是开源社区的三种模式,如果是企业选择项目,一般的我们会推荐 Apache、Linux 基金会的项目,它首先在法律上是没有隐患的,其次它是一个社区共识,我们可以通过向社区贡献这种方式获得更多的话语权,这样就形成了一个良性的循环。</p> <h3><strong>如何进入 Apache 孵化器</strong></h3> <p>Apache 现在有接近 50 个孵化器的项目,其中来自中国的有 10 个,APISIX 是现在国内唯一一个由创业公司进入 Apache 的项目,其他很多是来自华为、阿里、百度等大公司的项目。</p> <p>一个项目要想进入 Apache 孵化器,需要明白以下名词和步骤:</p> <ul> <li>Champion:他是你项目的引荐人,这是你首先要去联系到的最重要的角色,他需要熟悉你以及这个项目。</li> <li>Mentor:项目的导师,在项目进入 Apache 孵化器之后,Champion 就转换成导师的角色,一个项目至少需要 1 个 Champion,2 个 Mentor,所以至少需要找到 3 个合适的人选。Mentor 会指导项目怎么从孵化器里边的一个项目,一步一步地成长为 Apache 的顶级项目,这其中包括发布 Apache 版本、品牌管理、壮大社区等。如果变成了顶级项目,那么这个项目以后就是社区自治了。</li> <li>Proposal:找到 Champion 和 Mentor 之后,下一步需要写一个 Proposal,即一个提案,介绍我是谁,解决了什么问题,为什么要加入 Apache,项目现在的代码文件有没有和 Apache license 冲突的地方,初始的 committer 有哪些人、来自哪些公司,有没有什么潜在的风险,后面要如何发展等。</li> <li>Discuss:接着会发起一个讨论的邮件,看有多少人对这个感兴趣,这个阶段也可以找到一些有兴趣的 PMC 来加入 Mentor 指导项目。</li> <li>Vote:最后是投票,投票通过,项目就可以进入 Apache 孵化器了。</li> </ul> <h3><strong>Apache Way 和国内开源文化的冲突</strong></h3> <p>国内的 Apache 项目会遇到很多不一样的挑战,这和国内外的开源环境和文化有关系。</p> <p>比如国内的工程师会觉得太忙,经常 996 加班,所以没有时间和精力去做开源。但其实不少工程师在工作中也会使用到开源项目,写过开源项目的 feature,但是他们并不会主动提交 PR,也不会在写代码之前在社区内发起讨论,这就是很多文化上不同的地方。</p> <p>沟通方式上,Apache 提倡的是在邮件列表中公开讨论,但是国内的很多开发者更喜欢微信、QQ、电话等非公开的方式来沟通。特别是如果一个项目的 PPMC 大都来自同一家公司,那么可能开早会的时候就把一个功能敲定了,但在邮件列表里也没有出现,这就不符合 Apache 的文化。</p> <p>关于投票,很多开发者可能觉得自己英语不好或者人微言轻,不太愿意在邮件列表中发表自己的意见,也不参与投票,那么最终他就会被忽略掉。所以你需要积累一些”功绩“,去帮助别人,慢慢地增加自己的影响力。Apache 是一个由个人组成的基金会,每个人的行为都只代表自己,不代表公司,每个PMC 的一票都是对等的。</p> <p>关于 Title,在 Apache 里,最高的职位叫做 Apache 基金会主席,每个项目的管理者是 VP,然后是 PMC(项目管理员会)、 Committer、 Contributor ,他有一个类似于我们企业内职称晋级的通道。但是在 Apache里,即使你的职位变得很高,也并不意味着拥有更多的投票权,更多的是义务,比如对于 Apache APISIX 选举新的 committer 的投票而言,孵化器主席的票和我的票是一样的。Title 在 Apache 里面更多的是一种荣誉,因为 Apache 基金会是一个非营利组织,它强调的是贡献,这就是文化上的冲突。</p> <h3><strong>版权问题</strong></h3> <p>版权是一个非常重要的问题。一个项目在正式加入 Apache 孵化器之前,所有 committer 和公司都要签署 CLA,说明将这个项目的版权,全部捐给 Apache 基金会。加入 Apache 孵化器之后,最重要的里程碑就是发布第一个 Apache 的 Release,这个版本就是要理清 license 上的风险,让用户可以放心的使用。所以,商业公司使用 Apache 的项目是有保障的,不会涉及到版权不清晰的问题。</p> <p>最后,Apache APISIX 是一个正在快速发展的开源项目,希望大家多多参与和贡献。</p>

1.7.0(2019.12.24)

<li><code>A</code> 新增 数据库事务 API</li> <li><code>U</code> 更新 不再支持 node 8 以下运行环境,使用开发者工具本地调试需使用 node 8 或以上</li>

文言文编程语言来了,可是好像比英文更难写了!

<div class="content"> <p>近日有开发者开源了一门新的编程语言,吸引了大量开发者的目光,短短几天获得了 3k+ star。</p> <p><img height="207" src="https://static.oschina.net/uploads/space/2019/1218/143716_Ei0M_3820517.png" width="700" referrerpolicy="no-referrer"></p> <p>该语言名为“<a href="https://www.oschina.net/p/wenyan-lang">文言(wenyan-lang)</a>”,这是一门采用文言文输入的编程语言,该语言不包括英文字符,仅包含繁体中文字符,并保留繁体引号「」。</p> <p>先看看 Helloworld 怎么写:</p> <pre><code>吾有一數。曰三。名之曰「甲」。 為是「甲」遍。 吾有一言。曰「「問天地好在。」」。書之。 云云。 </code></pre> <p>这段代码等效于以下 JavaScript 代码:</p> <pre>var n = 3; for (var i = 0; i &lt; n; i++) { console.log("問天地好在。"); }</pre> <p>输出:</p> <pre><code>問天地好在。 問天地好在。 問天地好在。 </code></pre> <p>同时,标点和换行符是完全可选的,与古汉语一致,所以上面的代码等效于:</p> <pre><code>吾有一數曰三名之曰「甲」為是「甲」遍吾有一言曰「「問天地好在」」書之云云</code></pre> <p>Helloworld 看上去虽然简单,但是其实作为一门编程语言,这里的语法都有严格的定义:</p> <p>变量</p> <table> <thead> <tr> <th>wenyan</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td><code>吾有一數。曰三。名之曰「甲」。</code></td> <td><code>var a = 3;</code></td> </tr> <tr> <td><code>有數五十。名之曰「大衍」。</code></td> <td><code>var dayan = 50;</code></td> </tr> <tr> <td><code>昔之「甲」者。今「大衍」是也。</code></td> <td><code>a = dayan;</code></td> </tr> <tr> <td><code>吾有一言。曰「「噫吁戲」」。名之曰「乙」。</code></td> <td><code>var b = "alas!";</code></td> </tr> <tr> <td><code>吾有一爻。曰陰。名之曰「丙」。</code></td> <td><code>var c = false;</code></td> </tr> <tr> <td><code>吾有一列。名之曰「丁」。</code></td> <td><code>var d = [];</code></td> </tr> <tr> <td><code>吾有三數。曰一。曰三。曰五。名之曰「甲」曰「乙」曰「丙」。</code></td> <td><code>var a=1,b=3,c=5;</code></td> </tr> </tbody> </table> <p>控制</p> <table> <thead> <tr> <th>wenyan</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td><code>若三大於二者。乃得「「想當然耳」」也。</code></td> <td><code>if (3&gt;2){ return "of course"; }</code></td> </tr> <tr> <td><code>若三不大於五者。乃得「「想當然耳」」。若非。乃得「「怪哉」」也。</code></td> <td><code>if(3&lt;=5){return "of course"}else{return "no way"}</code></td> </tr> <tr> <td><code>為是百遍。⋯⋯ 云云。</code></td> <td><code>for (var i = 0; i &lt; 100; i++){ ... }</code></td> </tr> <tr> <td><code>恆為是。⋯⋯ 云云。</code></td> <td><code>while (true) { ... }</code></td> </tr> <tr> <td><code>凡「天地」中之「人」。⋯⋯ 云云。</code></td> <td><code>for (var human of world){ ... }</code></td> </tr> <tr> <td><code>乃止。</code></td> <td><code>break;</code></td> </tr> </tbody> </table> <p>运算</p> <table> <thead> <tr> <th>wenyan</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td><code>加一以二。</code></td> <td><code>1+2</code></td> </tr> <tr> <td><code>加一於二。</code></td> <td><code>2+1</code></td> </tr> <tr> <td><code>加一以二。乘其以三。</code></td> <td><code>(1+2)*3</code></td> </tr> <tr> <td><code>除十以三。所餘幾何。</code></td> <td><code>10%3</code></td> </tr> <tr> <td><code>減七百五十六以四百三十三。名之曰「甲」。</code></td> <td><code>var a = 756-433;</code></td> </tr> <tr> <td><code>夫「甲」「乙」中有陽乎。</code></td> <td><code>a || b</code></td> </tr> <tr> <td><code>夫「甲」「乙」中無陰乎。</code></td> <td><code>a &amp;&amp; b</code></td> </tr> </tbody> </table> <p>容器</p> <p>数组从 1 开始索引。</p> <table> <thead> <tr> <th>wenyan</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td><code>吾有一列。名之曰「甲」。充「甲」以四。以二。</code></td> <td><code>var a = []; a.push(4, 2);</code></td> </tr> <tr> <td><code>銜「甲」以「乙」。以「丙」</code></td> <td><code>a.concat(b).concat(c);</code></td> </tr> <tr> <td><code>夫「甲」之一。</code></td> <td><code>a[0]</code></td> </tr> <tr> <td><code>夫「甲」之其餘。</code></td> <td><code>a.slice(1);</code></td> </tr> <tr> <td><code>夫「玫瑰」之「「名」」。</code></td> <td><code>rose["name"]</code></td> </tr> <tr> <td><code>夫「寶劍」之長。</code></td> <td><code>sword.length;</code></td> </tr> </tbody> </table> <p>函数</p> <table> <thead> <tr> <th>wenyan</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td><code>吾有一術。名之曰「吸星大法」。是術曰。⋯⋯是謂「吸星大法」之術也。</code></td> <td><code>function f(){...}</code></td> </tr> <tr> <td><code>吾有一術。名之曰「六脈神劍」。欲行是術。必先得六數。曰「甲」。曰「乙」。曰「丙」。曰「丁」。曰「戊」。曰「己」乃行是術曰。⋯⋯是謂「六脈神劍」之術也。</code></td> <td><code>function f(a,b,c,d,e,f){...}</code></td> </tr> <tr> <td><code>吾有一術。名之曰「翻倍」。欲行是術。必先得一數。曰「甲」。乃行是術曰。乘「甲」以二。名之曰「乙」。乃得「乙」。是謂「翻倍」之術也。</code></td> <td><code>function double(a){var b = a * 2; return b;}</code></td> </tr> </tbody> </table> <p>但是作者表示上下文无关的语法描述还在构建中。</p> <p>文言语言采用自然语言处理共享了古典汉语语法,将语言编译为 JavaScript 或 Python,并且图灵完备。同时它不仅提供了一个配套在线 IDE,还提供了大量算法与数据结构示例,包括快排、汉诺塔问题与斐波那契数列表示等。</p> <p><img height="586" src="https://oscimg.oschina.net/oscnet/up-a4bf2895f376396e520d66768a4cf73b519.png" width="700" referrerpolicy="no-referrer"></p> </div>

TIOBE 12 月榜单:Java, C, Python 和 C# 谁能获得年度编程语言称号?

<div class="content"> <p>TIOBE&nbsp;<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">公布</a>了 12 月的编程语言排行榜。</p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/213317_0gXh_2720166.png" referrerpolicy="no-referrer"></p> <p><strong>TOP 20 的榜单如下:</strong></p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/214916_c76j_2720166.png" referrerpolicy="no-referrer"></p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/214937_pYOp_2720166.png" referrerpolicy="no-referrer"></p> <p><a href="https://www.oschina.net/news/111120/tiobe-index-201911" target="_blank">上个月</a>我们对排行榜排名前 10 的语言进行过简单的分析,虽然本月的前 10 依旧和上个月的保持一致,但细看的话,两者在数据上还是发生了微妙的变化。从上个月的数据来看,C 已经非常接近 Java,排在 Java 后指数仅差 0.2%。不过本月 Java 又再次拉开了与 C 的距离,它的指数已经超过&nbsp;C 多于&nbsp;1%。</p> <p>20 名内值得关注的编程语言还有 Go、Perl&nbsp;和&nbsp;Groovy。其中 Go 和 Perl 的排名与上个月相比都有了一定的进步,Go 由上个月的 20 升至本月的 15,Perl 由 21 升至 20。Groovy 的排名则出现了意外的下跌,由 14 下降至 22。</p> <p>Rust 的排名上个月从 34 上升到了 25,并创下历史新高,这个月又下降至 31,可谓是起起落落。虽然排名有所下降,不过它的热度却一直在线,毕竟上周微软还<a href="https://www.oschina.net/news/111801/microsoft-were-creating-a-new-rust-based-programming-language" target="_blank">宣布</a>正在开发基于 Rust 的安全编程语言。</p> <p>对了,TIOBE 将在下个月宣布 2019 年的年度编程语言,目前有四名候选者:Java (+1.3%), C (+1.8%), Python (+1.9%) 和&nbsp;C# (+1.4%),它们均在前 5 之内。同样是前 5 的 C++ 由于其指数稍有下降,所以未能进入候选。Python 是去年的年度编程语言,其流行度未见丝毫减弱之势,今年是否还能卫冕?C 凭借着物联网的兴起也有着不错的表现,那它有机会获得年度编程语言的称号吗?长期稳坐第一的 Java 呢?C# 也会有机会吗?毕竟它还没获得过此称号。</p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/224427_k0ee_2720166.png" referrerpolicy="no-referrer"></p> <p><strong>TOP 10 编程语言 TIOBE 指数走势(2002-2019)</strong></p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/230720_PCA9_2720166.png" referrerpolicy="no-referrer"></p> <p>继续看看 20 名后的排位:</p> <p><strong>第 21-50 名的编程语言排行</strong></p> <p><img src="https://static.oschina.net/uploads/space/2019/1209/230442_ABIi_2720166.png" referrerpolicy="no-referrer"></p> <p>第 51-100 名如下,由于它们之间的数值差异较小,仅以文本形式列出(按字母排序):</p> <blockquote> <p>(Visual) FoxPro, 4th Dimension/4D, ABC, Alice, Apex, Avenue, Awk, Bash, bc, Boo, Bourne shell, C++/CLI, CL (OS/400), Clojure, Common Lisp, Crystal, cT, Curl, Elixir, Erlang, Factor, Forth, Icon, Inform, Io, J, J#, Ladder Logic, Maple, Monkey, MQL4, NATURAL, OpenCL, Oz, PL/I, PostScript, Programming Without Coding Technology, Pure Data, Red, Ring, S, SPARK, Standard ML, Tcl, Vala/Genie, VBScript, VHDL, WebAssembly, XC, Z shell</p> </blockquote> <p>TIOBE 编程社区指数(The TIOBE Programming Community index)是编程语言流行度的指标,该榜单每月更新一次,指数基于全球技术工程师、课程和第三方供应商的数量。包括流行的搜索引擎,如谷歌、必应、雅虎、维基百科、亚马逊、YouTube 和百度都用于指数计算。具体的计算方式见这里:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2Fprogramming-languages-definition%2F" target="_blank">https://www.tiobe.com/tiobe-index/programming-languages-definition/</a>。</p> <p>值得注意的是,TIOBE 指数并不代表语言的好坏,开发者可以使用该榜单检查自身的编程技能是否需要更新,或者在开始构建新软件时对某一语言做出选择。</p> <p>详细榜单信息可以查看<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.tiobe.com%2Ftiobe-index%2F" target="_blank">&nbsp;TIOBE 官网</a>。</p> </div>

WASM 成为 HTML、CSS 与 JS 之后的第 4 门 Web 语言

<div class="content"> <p>大家都知道,万维网联盟&nbsp;W3C 认证的 Web 语言有 HTML、CSS 与 JavaScript,而近日联盟正式宣布 WebAssembly 核心规范(<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.w3.org%2FTR%2Fwasm-core-1%2F" target="_blank">WebAssembly Core Specification</a>)成为官方 Web 标准,这意味着 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.w3.org%2F2019%2F12%2Fpressrelease-wasm-rec.html.en" target="_blank">WebAssembly 成为了第 4 种 Web 语言</a>。</p> <p><img src="https://oscimg.oschina.net/oscnet/up-cc37fe2ed9f2f69413485764830775cb906.png" referrerpolicy="no-referrer"></p> <p>WebAssembly 也叫 WASM,它是为基于栈的虚拟机设计的二进制指令格式,WASM&nbsp;作为可移植目标,用于编译高级语言(如 C/C++/Rust),从而可以在&nbsp;Web 上部署高性能客户端和服务器应用,同时它也可以在许多其它环境中使用。</p> <p>WebAssembly 描述了一种内存安全的沙箱执行环境,该环境甚至可以在现有 JavaScript 虚拟机内部实现。当嵌入到 Web 中时,WebAssembly 将强制执行浏览器的同源和权限安全策略。</p> <p>WASM 有多种实现,包括浏览器和独立系统,它可以用于视频和音频编解码器、图形和 3D、多媒体和游戏、密码计算或便携式语言实现等应用。目前 1.0 版本的 Wasm 已经支持 Chrome、Firefox、Safari 与 Edge 浏览器。</p> <p>对于 Web 来说,因为其虚拟指令集设计,WebAssembly 可让加载的页面以本地编译代码运行,从而可以提高&nbsp;Web 性能。换句话说,WebAssembly 可以实现接近本地的性能,并且优化加载时间,同时最重要的是,它可以作为现有代码库的编译目标。</p> <p>尽管本地类型数量很少,但相对于 JavaScript 而言,性能的提高大部分归功于其对一致类型的使用。WebAssembly 对编译语言进行了数十年的优化,其字节代码针对紧凑性和流传输进行了优化。在下载其它代码时,网页便可以开始执行。网络和 API 访问通过附带的 JavaScript 库进行,安全模型则与&nbsp;JavaScript 相同。</p> <p>W3C 同时公布了 WASM 接下来的开发重点,新特性包括:</p> <ul> <li><strong>Threading,线程</strong>:Threading&nbsp;提供了共享内存多线程和原子内存访问的优势。</li> <li><strong>Fixed-width&nbsp;SIMD,固定宽度 SIMD</strong>:并行执行循环的向量操作。</li> <li><strong>Reference types 引用类型</strong>:允许 WebAssembly 代码直接引用宿主对象。</li> <li><strong>Tail calls,尾调用</strong>:直接调用而不使用额外的堆栈空间。</li> <li><strong>ECMAScript module integration,ECMAScript 模块集成</strong>:通过将 WebAssembly 可执行文件加载为 ES6 模块来与 JavaScript 进行交互。</li> </ul> <p>此外还有一些一直在跟进的特性,包括垃圾回收、调试接口与 WebAssembly 系统接口(WASI)等。</p> <p>值得一提的是,上个月 Mozilla、Fastly、Intel 与 Red Hat 宣布<a href="https://www.oschina.net/news/111316/announcing-the-bytecode-alliance">成立联合组织&nbsp;Bytecode Alliance(字节码联盟)</a>,该联盟旨在通过协作实施标准和提出新标准,以完善&nbsp;WebAssembly 在浏览器之外的生态。</p> </div>

微软正在开发基于 Rust 的安全编程语言

<div class="content"> <p>此前,<a href="https://www.oschina.net/news/108368/microsoft-is-exploring-to-use-rust-as-more-secure-code">微软表示正探索将 Rust 作为 C 和 C++ 的安全替代方案</a>,并且也对外展示了使用 Rust <a href="https://www.oschina.net/news/111173/microsofts-rust-experiment">重写 Windows 组件</a>的体验,根据微软的说法,Rust&nbsp;是一种从根本上考虑安全性的编程语言,他们将尝试使用 Rust&nbsp;重写各种产品,因为在过去的十年里,微软&nbsp;70% 以上的安全补丁都提供了与内存相关的错误,而 Rust&nbsp;正是解决这个问题的“良药”。</p> <p>而根据 <a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.zdnet.com%2Farticle%2Fmicrosoft-were-creating-a-new-rust-based-programming-language-for-secure-coding%2F" target="_blank">ZDNet 的报导</a>,近日在一次演讲中,谈到微软为解决相应内存问题所做的工作,微软研究人员&nbsp;Matthew Parkinson 提到了<strong>微软正在开发的基于 Rust 的新编程语言&nbsp;Verona</strong>。</p> <p><img height="153" src="https://static.oschina.net/uploads/space/2019/1203/081515_fwz1_3820517.png" width="700" referrerpolicy="no-referrer"></p> <p><span style="color:#080e14"><span style="background-color:#ffffff">演讲中,Matthew 先是分享了微软在 MemGC(Memory Garbage Collector)上所做的工作,MemGC 是指 IE 和&nbsp; Edge 浏览器上的内存垃圾回收器,它解决了标准浏览器一个特性——文档对象模型(DOM)中的漏洞,DOM 以</span></span>树结构表述了&nbsp;HTML 文档内容。&nbsp;</p> <p style="text-align:start">之后他由此牵出另一个问题:如何构建最安全的产品?不仅仅丢弃已有的东西,而要考虑可以在更加安全的系统中构建一些什么。他介绍了<span style="color:#080e14"><span style="background-color:#ffffff">微软正在使用 Rust 重写某些组件,并提出:“如果我们想要隔离,并精简遗留代码,以使攻击者的利用代码无法逃逸出来,那么如何设计语言呢?”&nbsp; &nbsp;&nbsp;</span></span></p> <p style="text-align:start"><span style="color:#080e14">Matthew 提出了</span>微软正在开发的基于 Rust 的新编程语言&nbsp;Verona,他表示这是首次讨论该项目,Verona 是用于微软“安全基础设施编程(safe infrastructure programming)”的一种新语言。</p> <p style="text-align:start"><span style="color:#080e14"><span style="background-color:#ffffff">Matthew 介绍,Verona 由 C#&nbsp;项目经理 Mads Torgensen 与 Microsoft Research Cambridge 研究软件工程师&nbsp;Juliana Franco 维护。&nbsp;</span></span></p> <p style="text-align:start"><span style="color:#080e14"><span style="background-color:#ffffff">微软面临的挑战是应对宽广的应用领域,范围从 C# 桌面应用到 C 或 C# Exchange、ASP.NET、Azure 与设备驱动程序,再到内存管理和启动加载器等底层 Windows 组件,以及 Windows 内核硬件抽象层(HAL,</span></span>hardware abstraction layer<span style="color:#080e14"><span style="background-color:#ffffff">)。&nbsp;</span></span></p> <p style="text-align:start"><span style="color:#080e14"><span style="background-color:#ffffff">“执行内存管理确实很困难,如果有任意并发突变,则临时内存安全性将非常困难”,Matthew 介绍了 Verona 的设计思路:“Verona&nbsp;</span></span><span style="color:#080e14"><span style="background-color:#ffffff">的所有权模型是基于对象组的,而不是像 Rust 那样基于单个对象的所有权模型。在 C++ 中,可以获得指针,并且它是基于对象的。但这与我关于数据和语法的思考不同,我认为数据结构是对象的集合,而对象的集合则是生命周期。&nbsp;因此,</span></span>通过在对象的所有权级别获得所有权<span style="color:#080e14"><span style="background-color:#ffffff">,我们就可以更接近人们正在使用的抽象级别,它使我们能够构建数据结构而不会超出安全范围。”</span></span></p> <blockquote> <p style="text-align:start">So by taking ownership at the level of ownership of objects, then we get much closer to the level of abstraction that people are using and it gives us the ability to build data structures without going outside of safety.</p> </blockquote> <p>另外,Matthew 还表示 Verona 很快将会开源。</p> </div>

投票通过,PHP 8 确认引入 Union Types 2.0

<div class="content"> <p>关于是否要在 PHP 8 中引入 Union Types 的投票已于近日结束,投票结果显示有 61&nbsp;名 PHP 开发组成员投了赞成票,5 名投了反对票。</p> <p><img src="https://static.oschina.net/uploads/space/2019/1112/041416_IP0K_2720166.png" referrerpolicy="no-referrer"></p> <p><img src="https://static.oschina.net/uploads/space/2019/1112/041957_MMxR_2720166.png" referrerpolicy="no-referrer"></p> <p>▲ (还留意到鸟哥在投票中投了反对票~)</p> <p>因此根据投票结果,官方已确认将会在 PHP 8 中引入&nbsp;Union Types 2.0。</p> <p><img src="https://static.oschina.net/uploads/space/2019/1111/180216_T7ww_2720166.png" referrerpolicy="no-referrer"></p> <p>关于&nbsp;Union Types 的具体讨论<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fphp%2Fphp-rfcs%2Fpull%2F1" target="_blank">可在 GitHub 查看</a>,下面我们来简单了解一下 Union Types(联合类型)。</p> <p>根据官方的介绍,Union Types(联合类型)支持接收多个不同类型的值,而不仅仅是单一类型。PHP 目前已经支持两种特殊的联合类型:</p> <ul> <li><code>Type</code>&nbsp;or&nbsp;<code>null</code>,使用特殊的<code>?Type</code>语法</li> <li><code>array</code>&nbsp;or&nbsp;<code>Traversable</code>,使特殊的<code>iterable</code>类型</li> </ul> <p>不过 PHP 目前尚不支持任意的联合类型。如要使用,需通过&nbsp;phpdoc 注释的帮助,示例如下:</p> <pre><code>class Number { /** * @var int|float $number */ private $number; /** * @param int|float $number */ public function setNumber($number) { $this-&gt;number = $number; } /** * @return int|float */ public function getNumber() { return $this-&gt;number; } }</code></pre> <p><img height="1" src="https://static.oschina.net/uploads/space/2019/1112/080215_H4lS_2720166.gif" width="1" referrerpolicy="no-referrer">根据<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwiki.php.net%2Frfc%2Funion_types_v2%23statistics" target="_blank">数据统计的结果</a>,在开源生态以及 PHP 自身的标准库中使用联合类型非常普遍。官方表示,如果 PHP 能支持联合类型,将会允许我们将更多类型信息从 phpdoc 迁移至函数签名,这具有以下常见的优点:</p> <ul> <li>类型实际上是强制执行的,因此可以及早发现错误。</li> <li>因为它们是强制性的,所以类型信息不太可能变得过时或遗漏边缘情况。</li> <li>在继承过程中会检查类型,以执行里氏替换原则(Liskov Substitution Principle)</li> <li>可通过反射获得类型信息。</li> <li>语法比 phpdoc 简洁。</li> </ul> <p>泛型之后,联合类型可以说是目前类型声明系统中最大的“缺口”。</p> <h3><strong>提案</strong></h3> <p>联合类型使用&nbsp;<code>T1|T2|…</code>&nbsp;语法,可在所有接受的类型中使用:</p> <pre><code>class Number { private int|float $number; public function setNumber(int|float $number): void { $this-&gt;number = $number; } public function getNumber(): int|float { return $this-&gt;number; } }</code></pre> <p><img height="1" src="https://static.oschina.net/uploads/space/2019/1112/080215_E40E_2720166.gif" width="1" referrerpolicy="no-referrer"><strong>支持的类型</strong></p> <p>联合类型支持 PHP 当前支持的所有类型:空类型、可空联合类型、false pseudo-type、重复和冗余类型。</p> <h4><strong>类型语法</strong></h4> <p>除特殊<code>void</code>类型外,PHP 的类型语法现在可以通过以下语法来描述:</p> <pre><code>type: simple_type | "?" simple_type | union_type ; union_type: simple_type "|" simple_type | union_type "|" simple_type ; simple_type: "false" # only legal in unions | "null" # only legal in unions | "bool" | "int" | "float" | "string" | "array" | "object" | "iterable" | "callable" # not legal in property types | "self" | "parent" | namespaced_name ;</code></pre> </div>

从 Java 迁移到 Kotlin

<div class="content"> <p>5 月份的 Google I/O 上,<a href="https://www.oschina.net/news/106516/google-next-big-step-kotlin-first">谷歌宣布 Kotlin-first</a>,Kotlin 开始成为 Android 开发者的首选语言,接着谷歌针对 Kotlin 的相关建设不断。</p> <p>本文简介了谷歌推出的 Kotlin 迁移指南相关内容。</p> <p><strong>开始从 Java 迁移到&nbsp;Kotlin</strong></p> <p>谷歌建议“由点及面”进行迁移,包括人与代码两个层面:</p> <p>人的层面,团队内部推举出一位 Kotlin “推广大使”,他需要做到:</p> <ul> <li> <p>了解 Kotlin 并成为专家</p> </li> <li> <p>制定 Kotlin 开发流程</p> </li> <li> <p>参与代码审核,确保 Kotlin 开发流程得到顺利贯彻</p> </li> <li> <p>组建公司/团队内部的学习小组</p> </li> <li> <p>主动收集内部的问题并与开发者社区反馈探讨</p> </li> </ul> <p>而代码层面,需要做到:</p> <ul> <li> <p>先使用 Kotlin 编写测试(此时项目依然在使用 Java)</p> </li> <li> <p>使用 Kotlin 编写新代码</p> </li> <li> <p>将现有代码更新为 Kotlin 代码</p> </li> </ul> <p>在将现有代码更新为 Kotlin 代码时,可以直接使用 Android Studio 提供的&nbsp;Java 转 Kotlin 工具,只需要在菜单中选择 “Code → Convert Java File to Kotlin File” 即可:</p> <p><img height="581" src="https://oscimg.oschina.net/oscnet/916bce8f15b834d747d849be9942268c606.jpg" width="279" referrerpolicy="no-referrer"></p> <p>△ Java 转 Kotlin 工具</p> <p>另外,Kotlin 的迁移工作没必要一蹴而就,开发者可以按照自己的进度来逐步进行迁移。一个项目中可以同时包含 .java 和 .kt 文件,但同一个文件中不可以既有 Java 也有 Kotlin,开发者也可以在 Kotlin 代码中使用任何 Java 库。</p> <p><img height="390" src="https://oscimg.oschina.net/oscnet/7ffabcfe6e1d8f7b9e0fb47576c7c72d536.jpg" width="400" referrerpolicy="no-referrer"></p> <p>△ Android Studio 的 Lint 检查中也可以打开 Kotlin 互操作检查</p> <p>在 Kotlin 中调用&nbsp;Java 时,有几点需要注意:</p> <ul> <li> <p>将代码标记为 @Nullable 和 @NotNull</p> </li> <li> <p>使用属性前缀(getName(), setName(), isActive())</p> </li> <li> <p>避免使用 Kotlin 中的保留关键字(when、is 等)</p> </li> </ul> <p>更详细的 Kotlin 互操作指南可以查看&nbsp;Android 开发者网站:</p> <p>https://developer.android.google.cn/kotlin/interop</p> <p>内容引用自:<a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzAwODY4OTk2Mg%3D%3D%26mid%3D2652051237%26idx%3D1%26sn%3D72ef09b5fbc3b6967abf858d38ec6ed1%26chksm%3D808cb760b7fb3e765137f09a85ada30fab7262c78b0edaca73399b8ee1128bb879b35ea52731%26mpshare%3D1%26scene%3D1%26srcid%3D%26sharer_sharetime%3D1573004492613%26sharer_shareid%3D0af8c6587378392ee184f19b3058c11d%26pass_ticket%3Dbrg3vmHVVoWKszGVVmZC8k4XoqkKgizVIhf0vjMSGKJEXBvXHgksCc1AhH7UXPCo%23rd" target="_blank">谷歌开发者&nbsp;</a></p> </div>

dart2native,Dart 程序现在可以预编译为本地可执行代码

<div class="content"> <p>Google 在 Dart 语言 2.6 版本中带来了一个 dart2native 编译功能,这是其现有编译器的扩展,能够将 Dart 程序编译为包含预编译的机器码的自包含可执行文件。</p> <p><img src="https://oscimg.oschina.net/oscnet/4c51d764a6b7a9d0e8687c915eb045771c9.jpg" referrerpolicy="no-referrer"></p> <p>这一功能能够使开发人员使用 Dart 在 macOS、Windows 或 Linux 上创建命令行工具,而此前 Dart 仅适用于 iOS 和 Android 移动设备。</p> <p>这些自包含的可执行文件可以在未安装 Dart SDK 的计算机上运行,​​并且可以在几毫秒内开始运行,同时当编译为本地代码时,可以使用 Dart 相同的核心库集。</p> <p>开发人员表示使用本机代码可以将 Docker 镜像的大小减少 90% 以上。&nbsp;</p> <p>不过目前 dart2native 编译器还不完善,比如当前还没有提供交叉编译支持。</p> <p>详情查看:</p> <p><a href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmedium.com%2Fdartlang%2Fdart2native-a76c815e6baf" target="_blank">https://medium.com/dartlang/dart2native-a76c815e6baf</a></p> </div>

1.3.0(2019.10.14)

<li><code>A</code> 新增 查询操作符:all, elemMatch, exists, size, mod</li> <li><code>A</code> 新增 更新操作符:push, pull, pullAll, addToSet, rename, max, min</li> <li><code>A</code> 新增 聚合流水线阶段:lookup</li> <li><code>A</code> 新增 可用于聚合流水线 lookup 阶段的 pipeline 操作符</li>

1.3.1(2019.10.14)

<li><code>F</code> 修复 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/database/command.push.html" target="_blank" rel="noopener noreferrer"><code>push</code><span></span></a> 操作符接收数组参数的问题</li>

1.2.2(2019.10.11)

<li><code>F</code> 修复 使用 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/constant/constant.html" target="_blank" rel="noopener noreferrer"><code>DYNAMIC_CURRENT_ENV</code><span></span></a> 时发起云调用会失败的问题</li>

1.1.1(2019.09.12)

<li><code>A</code> 新增 常量 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/constant/constant.html" target="_blank" rel="noopener noreferrer"><code>DYNAMIC_CURRENT_ENV</code><span></span></a></li>

v0.8.1(2019.07.01)

<li><code>A</code> 增加 数据库聚合能力 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/aggregation/aggregation.html" target="_blank" rel="noopener noreferrer">详情<span></span></a></li>

2019.06.25(>=1.02.1906252)

<li><code>A</code> 新增 云控制台支持云函数接收消息推送配置 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/openapi/openapi.html#usecase-callback" target="_blank" rel="noopener noreferrer">详情<span></span></a></li>

v0.6.0(2019.05.30)

<li><code>A</code> 新增 云函数内可通过 <code>getWXContext</code> 获取当前所在环境 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/utils/Cloud.getWXContext.html" target="_blank" rel="noopener noreferrer">详情<span></span></a></li>

2019.05.24(>=1.02.1905242)

<li><code>A</code> 新增 IDE 支持云函数增量上传(上传单文件或目录)</li> <li><code>A</code> 新增 云函数本地调试支持开发数据调用 <a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/openapi/openapi.html">详情</a></li>