如何在 APISIX Ingress Controller 中运行 Go 插件运行器

如标题所说。

背景描述

在社区中闲逛时,我发现有用户对"如何在 APISIX Ingress 环境中使用多语言插件"感到困惑。我恰好是 go-plugin-runner 的用户,对 APISIX Ingress 项目也有一些了解,于是就有了这份文档。

方案描述

基于 go-plugin-runner 插件的 0.3 版本和 APISIX Ingress 的 1.4.0 版本,本文通过构建集群、构建镜像、自定义 helm chart 包,最后部署资源的步骤。保证完全基于本文档可以推导出最终结果。

bash
go-plugin-runner: 0.3
APISIX Ingress: 1.4.0
kind: kind v0.12.0 go1.17.8 linux/amd64
kubectl version: Client Version: v1.23.5/Server Version: v1.23.4
golang: go1.18 linux/amd64

开始

构建集群环境

选择 kind 来构建本地集群环境。命令如下:

bash
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
EOF

构建 go-plugin-runner 可执行文件

如果你已经完成了插件的编写,就可以开始编译可执行文件来与 APISIX 一起运行。

本文推荐两种打包构建选项。

  1. 将打包过程放入 Dockerfile 中,在后续构建 docker 镜像时完成编译过程。
  2. 你也可以按照本文档使用的方案,先构建可执行文件,然后将打包的可执行文件复制到镜像中。

如何选择方案应该根据你的本地硬件考虑。这里选择第二种方案的原因是,我想依托我强大的本地硬件来提高构建速度,加快流程。

进入 go-plugin-runner 目录

选择一个文件夹地址 /home/chever/api7/cloud_native/tasks/plugin-runner,将我们的 apisix-go-plugin-runner 项目放置在这个文件夹中。

成功放置后,文件树如下所示:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner$ tree -L 1
.
└── apisix-go-plugin-runner
1 directory, 0 files

然后你需要进入 apisix-go-plugin-runner/cmd/go-runner/plugins 目录,在该目录中编写你需要的插件。本文将使用默认插件 say 进行演示。

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner/apisix-go-plugin-runner$ tree cmd
cmd
└── go-runner
├── main.go
├── main_test.go
├── plugins
│ ├── fault_injection.go
│ ├── fault_injection_test.go
│ ├── limit_req.go
│ ├── limit_req_test.go
│ ├── say.go
│ └── say_test.go
└── version.go
2 directories, 10 files

编写完插件后,正式开始编译可执行文件,这里注意应该构建静态可执行文件,而不是动态的。

包编译命令如下。

bash
CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' .

这样就成功打包了一个静态编译的 go-runner 可执行文件。

apisix-go-plugin-runner/cmd/go-runner/ 目录中,你可以看到当前文件树如下所示:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner/apisix-go-plugin-runner/cmd/go-runner$ tree -L 1
.
├── go-runner
├── main.go
├── main_test.go
├── plugins
└── version.go
1 directory, 4 files

请记住路径 apisix-go-plugin-runner/cmd/go-runner/go-runner,我们稍后会用到它。

构建 Docker 镜像

这里构建镜像是为了后续使用 helm 安装 APISIX 做准备。

编写 Dockerfile

回到路径 /home/chever/api7/cloud_native/tasks/plugin-runner,在该目录中创建一个 Dockerfile,这里给出一个演示。

dockerfile
ARG ENABLE_PROXY=false
# Build Apache APISIX
FROM api7/apisix-base:1.19.9.1.5
ADD ./apisix-go-plugin-runner /usr/local/apisix-go-plugin-runner
ARG APISIX_VERSION=2.13.1
LABEL apisix_version="${APISIX_VERSION}"
ARG ENABLE_PROXY
RUN set -x \
&& (test "${ENABLE_PROXY}" != "true" || /bin/sed -i 's,http://dl-cdn.alpinelinux.org,https://mirrors.aliyun.com,g' /etc/apk/repositories) \
&& apk add --no-cache --virtual .builddeps \
build-base \
automake \
autoconf \
make \
libtool \
pkgconfig \
cmake \
unzip \
curl \
openssl \
git \
openldap-dev \
&& luarocks install https://github.com/apache/apisix/raw/master/rockspec/apisix-${APISIX_VERSION}-0.rockspec --tree=/usr/local/apisix/deps PCRE_DIR=/usr/local/openresty/pcre \
&& cp -v /usr/local/apisix/deps/lib/luarocks/rocks-5.1/apisix/${APISIX_VERSION}-0/bin/apisix /usr/bin/ \
&& (function ver_lt { [ "$1" = "$2" ] && return 1 || [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1"`" ]; }; if [ "$APISIX_VERSION" = "master" ] || ver_lt 2.2.0 $APISIX_VERSION; then echo 'use shell ';else bin='#! /usr/local/openresty/luajit/bin/luajit\npackage.path = "/usr/local/apisix/?.lua;" .. package.path'; sed -i "1s@.*@$bin@" /usr/bin/apisix ; fi;) \
&& mv /usr/local/apisix/deps/share/lua/5.1/apisix /usr/local/apisix \
&& apk del .builddeps \
&& apk add --no-cache \
bash \
curl \
libstdc++ \
openldap \
tzdata \
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /usr/local/apisix/logs/access.log \
&& ln -sf /dev/stderr /usr/local/apisix/logs/error.log
WORKDIR /usr/local/apisix
ENV PATH=$PATH:/usr/local/openresty/luajit/bin:/usr/local/openresty/nginx/sbin:/usr/local/openresty/bin
EXPOSE 9080 9443
CMD ["sh", "-c", "/usr/bin/apisix init && /usr/bin/apisix init_etcd && /usr/local/openresty/bin/openresty -p /usr/local/apisix -g 'daemon off;'"]
STOPSIGNAL SIGQUIT

这份 Dockerfile 配置文档,来源于这个链接。我做的唯一修改如下:

bash
ARG ENABLE_PROXY=false
# Build Apache APISIX
FROM api7/apisix-base:1.19.9.1.5
ADD ./apisix-go-plugin-runner /usr/local/apisix-go-plugin-runner
ARG APISIX_VERSION=2.13.1
LABEL apisix_version="${APISIX_VERSION}"
ARG ENABLE_PROXY

/home/chever/api7/cloud_native/tasks/plugin-runner 目录中的所有 /apisix-go-plugin-runner 文件打包到 Docker 镜像中。注意可执行文件 apisix-go-plugin-runner/cmd/go-runner/go-runner 的位置和上面 Dockerfile 中 /usr/local/apisix-go-plugin-runner 目录的位置,得出可执行文件在 Docker 镜像中的最终位置如下。

bash
/usr/local/apisix-go-plugin-runner/cmd/go-runner/go-runner

请记住这个地址。我们将在其余配置中使用它。

开始构建 Docker 镜像

基于 Dockerfile 开始构建 Docker 镜像。命令在 /home/chever/api7/cloud_native/tasks/plugin-runner 目录中执行。命令如下:

bash
docker build -t apisix/forrunner:0.1 .

命令解释:构建一个名为 apisix/forrunner 的镜像,并标记为 0.1 版本。

将镜像加载到集群环境

bash
kind load docker-image apisix/forrunner:0.1

将镜像加载到 kind 集群环境中,以便在 helm 安装过程中拉取自定义本地镜像进行安装。

安装 APISIX Ingress

自定义 helm chart

这一部分重点是修改官方 helm 包中的 values.yaml 文件,使其能够安装本地打包的镜像,并正确运行 go-plugin-runner 可执行文件。

获取官方 helm chart

首先,用以下命令获取最新的 apisix helm chart 包:

bash
helm fetch apisix/apisix

文件树如下:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner$ tree -L 1
.
├── apisix-0.9.1.tgz
└── apisix-go-plugin-runner
1 directory, 1 file
解压

解压 apisix-0.9.1.tgz 文件,准备重写配置。解压命令如下。

bash
tar zxvf apisix-0.9.1.tgz

文件树如下:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner$ tree -L 1
.
├── apisix
├── apisix-0.9.1.tgz
└── apisix-go-plugin-runner
2 directories, 1 file
更改 values.yaml

进入 apisix 文件夹,修改 values.yaml 文件。两处更改如下:

yaml
image:
repository: apisix/forrunner
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: 0.1

第一处更改将 helm 安装的镜像设置为你自己本地打包的镜像。

yaml
extPlugin:
enabled: true
cmd: ["/usr/local/apisix-go-plugin-runner/cmd/go-runner/go-runner", "run"]

第二处更改设置了运行容器后 go-runner 在容器中的位置。

压缩修改后的 helm chart

配置完成后,压缩 apisix 文件。压缩命令如下:

bash
tar zcvf apisix.tgz apisix/

获得压缩文件,此时文件树如下:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner$ tree -L 1
.
├── apisix
├── apisix-0.9.1.tgz
├── apisix-go-plugin-runner
└── apisix.tgz
2 directories, 2 files

执行 helm 安装命令

创建命名空间

安装前,先用以下命令创建命名空间:

bash
kubectl create ns ingress-apisix

然后使用 helm 安装 APISIX,命令如下:

bash
helm install apisix ./apisix.tgz --set gateway.type=NodePort --set ingress-controller.enabled=true --namespace ingress-apisix --set ingress-controller.config.apisix.serviceNamespace=ingress-apisix

创建 httpbin 服务和 ApisixRoute 资源

创建一个 httpbin 后端资源,与部署的 ApisixRoute 资源一起运行,以测试功能是否正常工作。

创建 httpbin 服务

用以下命令创建 httpbin 服务:

bash
kubectl run httpbin --image kennethreitz/httpbin --port 80

用以下命令暴露端口:

bash
kubectl expose pod httpbin --port 80

创建 ApisixRoute 资源

创建 go-plugin-runner-route.yaml 文件来启用 ApisixRoute 资源,配置文件如下:

yaml
apiVersion: apisix.apache.org/v2beta3
kind: ApisixRoute
metadata:
name: plugin-runner-demo
spec:
http:
- name: rule1
match:
hosts:
- local.httpbin.org
paths:
- /get
backends:
- serviceName: httpbin
servicePort: 80
plugins:
- name: ext-plugin-pre-req
enable: true
config:
conf:
- name: "say"
value: "{\"body\": \"hello\"}"

创建资源的命令如下:

bash
kubectl apply -f go-plugin-runner-route.yaml

测试

用以下命令测试用 Golang 编写的插件是否正常工作:

bash
kubectl exec -it -n ${namespace of Apache APISIX} ${Pod name of Apache APISIX} -- curl http://127.0.0.1:9080/get -H 'Host: local.httpbin.org'

这里我从 kubectl get pods --all-namespaces 命令推导出 ${namespace of Apache APISIX}${Pod name of Apache APISIX} 参数分别是 ingress-apisixapisix-55d476c64-s5lzw,执行命令如下:

bash
kubectl exec -it -n ingress-apisix apisix-55d476c64-s5lzw -- curl http://127.0.0.1:9080/get -H 'Host: local.httpbin.org'

期望得到的响应是:

bash
chever@cloud-native-01:~/api7/cloud_native/tasks/plugin-runner$ kubectl exec -it -n ingress-apisix apisix-55d476c64-s5lzw -- curl http://127.0.0.1:9080/get -H 'Host: local.httpbin.org'
Defaulted container "apisix" out of: apisix, wait-etcd (init)
hello

Comments