[date: 2022-03-27 12:23] [visits: 6]

动手系列之部署APISIX到K8S

为更好的理论与实践相结合,后续计划不定期开展一些动手实验并记录其过程,实践执行标准将尽量满足合理的工程性要求,其最终目标是拥有一个完整微服务K8S集群,包括微服务模块(业务)、网关(路由,限流,灰度等)、链路追踪、日志平台、监控、大数据平台等。

开始

APISIX是一个与KONG基本功能相同的网关技术选型方案,但作为后来者似乎有更多优点,这两者最重要的作用是提供插件开发框架定义与生态,让网关能力扩展任性而为。

第一次实践本应是搭建K8S集群,但笔者之前在本地WSL中搭建K8S集群时解决的各种问题未做记录,因此后续实践将以K8S集群存在为前提进行。

实践若以云服务K8S集群进行,作为个人较难承担相关资源成本,因而选择使用本地K8S集群进行实验,这将导致部分行为与实际工作有所差异,尤其集中在运维方面

实践

有K8S集群后以部署APISIX到K8S集群作为第一次实践,本次实践完成后应达到能从浏览器可访问K8S中APISIX的目标,而考虑后续需进行插件开发,笔者选择使用Dockerfile构建镜像部署APISIX到K8S集群这种方式。

首先通过命令kubectl create namespace lab在K8S中创建一个专门用于实践的namespace,此后的实践内容也将部署在此namespace下。

Etcd

APISIX需要使用Etcd进行配置存储,先部署一个Etcd:

# etcd.yaml

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: lab
  labels:
    app: etcd
    etcd_node: etcd
spec:
  containers:
  - command:
    - etcd
    - --name=etcd
    - --data-dir=/var/k8s-lab/etcd
    - --advertise-client-urls=http://etcd:2379
    - --initial-advertise-peer-urls=http://etcd:2380
    - --listen-client-urls=http://0.0.0.0:2379
    - --listen-peer-urls=http://0.0.0.0:2380
    - --initial-cluster=etcd=http://etcd:2380
    image: k8s.gcr.io/etcd:3.5.0-0
    imagePullPolicy: IfNotPresent
    name: etcd
    volumeMounts:
    - mountPath: /var/k8s-lab/etcd
      name: etcd
    ports:
    - containerPort: 2379
      name: client
      protocol: TCP
    - containerPort: 2380
      name: server
      protocol: TCP
  volumes:
  - hostPath:
      path: /var/k8s-lab/etcd
      type: DirectoryOrCreate
    name: etcd
  restartPolicy: Always

---

apiVersion: v1
kind: Service
metadata:
  name: etcd-client
  namespace: lab
spec:
  ports:
  - name: etcd-client-port
    port: 2379
    protocol: TCP
    targetPort: 2379
  selector:
    app: etcd

将上述内容保存为etcd.yaml,然后执行kubectl apply -f etcd.yaml完成Etcd部署,此处可参考文章验证部分的内容,提前确认Etcd正确部署。

此处映射了主机目录/var/k8s-lab/etcd作为容器的数据目录

网关代码

为了扩展与可维护性,网关需要有自己的配置文件、Dockerfile、插件等内容,因此先为网关设计一个Git仓库,目录结构暂设计如下:

$ tree .
.
└── apisix // 网关内容,平级目录可能还会加入其他实践内容
    ├── Dockerfile // build Docker镜像
    ├── conf // 配置文件目录
    │   └── apisix.yaml // 定义apisix配置信息如etcd、prometheus、admin等
    └── extra // 插件开发

相关文件内容:

# apisix.yaml

etcd:
  host:
    - http://etcd-client.lab.svc.cluster.local:2379
apisix:
  allow_admin:
    - 0.0.0.0/0
# Dockerfile

FROM apache/apisix:2.13.0-alpine
copy conf/apisix.yaml /usr/local/apisix/conf/config.yaml
expose 9080 9091 9443

构建镜像

有了Git仓库后,可配置CICD用于自动构建镜像与部署,但此处笔者没有稳定的云K8S环境因而无法实现自动部署,暂时只是借助CICD构建镜像再使用手动方式部署到K8S集群。对于个人开发者笔者推荐使用Gitlab的CICD集成,此处不过多描述如何安装gitlab-runner,只是把构建APISIX镜像的CICD配置文件内容贴一下供参考:

# .gitlab-ci.yml

image: docker:stable

before_script:
  - docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY

stages:
  - build
  - push

build:
  stage: build
  script:
    - docker build --pull -t $REGISTRY/$SPACE_NAME:$CI_COMMIT_REF_NAME apisix
  only:
    refs:
      - /^release.apisix.+/
  tags:
    - xxx

push:
  stage: push
  script:
    - docker push $REGISTRY/$SPACE_NAME:$CI_COMMIT_REF_NAME
  only:
    refs:
      - /^release.apisix.+/
  when: manual
  tags:
    - xxx

通过此配置文件与已注册好的gitlab-runner可达到效果:“当推送的分支格式如release.apisix.xxx时,build过程会自动运行并得到一个Docker镜像,而push过程则是手动触发将得到的Docker镜像推送到远端仓库”。由此每次发布网关版本时将得到一个Docker镜像,而后可通过手动操作将此镜像部署到K8S集群。

此处笔者将push过程指定为手动触发,是因为一些个人项目中用于build镜像的VPS即是运行的VPS,这样做可以节省带宽以及加快发布速度,避免push=>pull=>run

部署镜像到K8S集群

# apisix.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: apisix
  namespace: lab
  labels:
    app: apisix
spec:
  replicas: 2
  selector:
    matchLabels:
      app: apisix
  template:
    metadata:
      labels:
        app: apisix
    spec:
      initContainers:
        - command:
            - /bin/sh
            - -c
            - |
              sysctl -w net.core.somaxconn=65535
              sysctl -w net.ipv4.ip_local_port_range="1024 65535"
              sysctl -w net.ipv4.tcp_max_syn_backlog=8192
              sysctl -w fs.file-max=1048576
              sysctl -w fs.inotify.max_user_instances=16384
              sysctl -w fs.inotify.max_user_watches=524288
              sysctl -w fs.inotify.max_queued_events=16384
          image: busybox:latest
          imagePullPolicy: IfNotPresent
          name: init-sysctl
          resources: {}
          securityContext:
            privileged: true
            procMount: Default
      restartPolicy: Always
      containers:
        - env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
          image: "" # 此处配置前部分构建的网关镜像地址
          imagePullPolicy: IfNotPresent
          name: apisix
          ports:
            - containerPort: 9080
              name: http
              protocol: TCP
            - containerPort: 9443
              name: https
              protocol: TCP
            - containerPort: 9091
              name: prometheus
              protocol: TCP
          readinessProbe:
            failureThreshold: 6
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            tcpSocket:
              port: 9080
            timeoutSeconds: 1
          volumeMounts:
            - mountPath: /etc/localtime
              name: localtime
              readOnly: true
      volumes:
        - hostPath:
            path: /etc/localtime
            type: File
          name: localtime

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: apisix-dashboard-config
  namespace: lab
data:
  conf.yaml: |
    conf:
      listen:
        port: 9000
      allow_list:
        - 0.0.0.0/0
      etcd:
        endpoints:
          - etcd-client.lab.svc.cluster.local:2379
    authentication:
      secret: secret
      expire_time: 2592000
      users:
        - username: admin 
          password: admin

---

apiVersion: v1
kind: Pod
metadata:
  name: apisix-dashboard
  namespace: lab
  labels:
    app: apisix-dashboard
spec:
  containers:
  - image: apache/apisix-dashboard:2.11-alpine
    name: apisix-dashboard
    ports:
    - containerPort: 9000
      name: client
      protocol: TCP
    volumeMounts:
    - name: apisix-dashboard-config
      mountPath: /usr/local/apisix-dashboard/conf/conf.yaml
      subPath: conf.yaml
  volumes:
    - name: apisix-dashboard-config
      configMap:
        name: apisix-dashboard-config
  restartPolicy: Always

---

apiVersion: v1
kind: Service
metadata:
  name: apisix
  namespace: lab
  labels:
    app: apisix
spec:
  ports:
    - name: http
      port: 9080
      protocol: TCP
      targetPort: 9080
    - name: https
      port: 9443
      protocol: TCP
      targetPort: 9443
    - name: prometheus
      port: 9091
      protocol: TCP
      targetPort: 9091
  selector:
    app: apisix

---

apiVersion: v1
kind: Service
metadata:
  name: apisix-dashboard
  namespace: lab
spec:
  ports:
  - name: apisix-dashboard-port
    port: 9000
    protocol: TCP
    targetPort: 9000
  selector:
    app: apisix-dashboard

上述内容描述了APISIX的K8S相关资源:

将内容保存为apisix.yaml并执行命令kubectl apply -f apisix.yaml,接着K8S会完成部署运行。

验证

等待一段时间后执行命令进行验证:

$ kubectl -n lab get pods
NAME                     READY   STATUS    RESTARTS      AGE
apisix-b47bff658-6wx2l   1/1     Running   0             88m
apisix-b47bff658-g4v55   1/1     Running   0             88m
apisix-dashboard         1/1     Running   0             31m
etcd                     1/1     Running   0             166m

$ kubectl -n lab get svc
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
apisix             ClusterIP   10.109.76.252   <none>        9080/TCP,9443/TCP,9091/TCP   88m
apisix-dashboard   ClusterIP   10.99.158.192   <none>        9000/TCP                     86m
etcd-client        ClusterIP   10.108.69.57    <none>        2379/TCP                     166m

执行命令验证各服务访问:

$ curl http://10.108.69.57:2379/version
{"etcdserver":"3.5.0","etcdcluster":"3.5.0"}#

$ curl http://10.109.76.252:9080
{"error_msg":"404 Route Not Found"}

$ curl http://10.99.158.192:9000
...

至此笔者达成了部署APISIX到本地K8S集群的目标,且留下了扩展APISIX的途径,下一次实践笔者将尝试在APISIX内实现一个自定义插件以及配置网关路由规则。

主机访问APISIX

笔者的K8S集群搭建在WSL中,从Windows主机访问WSL的网络存在一些约束(未理论学习),且WSL中K8S网络也还是一个独立的网络空间,因此在WLS的K8S中部署APISIX后还不能从浏览器访问相关服务,为解决此问题笔者做了如下两个措施:

通过网上搜索的一行命令:netsh interface ip add address "vEthernet (WSL)" 192.168.168.188 255.255.255.0,在Windowns下执行此命令后可以使用192.168.168.188访问到WSL

主机能访问WSL网络但无法直接访问K8S服务,选择安装Nginx进行请求转发,类似云服务接入商的Load Balancer

另外笔者修改了本地hosts文件让Nginx支持更灵活的转发以及可在浏览器中通过域名访问服务。

hosts:

192.168.168.168 lab.com
192.168.168.168 admin.lab.com

Nginx转发:

server {
    listen 80;
    server_name lab.com;

    location / {
        proxy_pass http://10.109.76.252:9080;
    }
}

server {
    listen 80;
    server_name admin.lab.com;

    location / {
        proxy_pass http://10.99.158.192:9000;
    }
}

经过上述措施后主机能顺利通过浏览器访问http://lab.comhttp://admin.lab.com