背景

我们公有云的资源通常是很宝贵的,有时候申请的资源不足以运行大规模应用,本文描述了一种利用frp进行内网穿透,通过ingress整合访问路径的方法。全部模块通过K8S部署,易于测试和拓展。
开始按照本方案部署前,请确认您在本地和远程都成功部署了K8S集群,且远程K8S集群已经部署了Ingress控制器。

总体方案介绍

设计思路

假设我们拥有my.domain.com这个域名,我们可以在公有云的Ingress中按照以下规则设计:

  • my.domain.com/local:指向本地K8S集群中的Web应用(通常意味着较多的计算和存储资源)
  • my.domain.com/和其他路径:指向远程K8S集群中的Web应用(因为公有云资源有限,只能运行轻量应用)

部署架构

系统的部署架构如下所示:

rancher 创建ingress_nginx

在上述架构中,在远程K8S集群中部署frps容器,frps容器开放两个端口:

  • bind_port:7000端口,监听来自本地K8S集群的注册请求
  • vhost_http_port:8080端口,承载远程K8S集群对本地K8S集群的Web访问

在本地K8S集群中部署frpc容器,负责按照url规则将访问请求派发到本地集群的web服务器中。

远程K8S集群中部署Ingress和frps

配置的内容包括:

  • 创建frps容器,并分别为8080端口和7000端口创建Service,其中7000端口的服务类型为NodePort,使用节点服务器的32890端口;
  • 在Ingress中配置/local路径,使其指向8080端口的服务;

frps pod和service配置为:

apiVersion: v1
kind: ConfigMap
metadata:
 name: frps-conf
 namespace: <namespace>
 labels:
   app: frps
data:
 frps.ini: |-
   [common]
   bind_port = 7000
   token = <token>
   vhost_http_port = 8080
   vhost_https_port = 8443
   max_pool_count = 5
   log_file = /var/log/frps/frps.log
   log_level = info
   log_max_days = 7
---
kind: Service
apiVersion: v1
metadata:
 labels:
   app: frps
 name: frps
 namespace: <namespace>
spec:
 type: NodePort
 ports:
 - port: 32890
   targetPort: 7000
   name: frps
   nodePort: 32890
 selector:
   app: frps
---
kind: Service
apiVersion: v1
metadata:
 labels:
   app: frps
 name: frps-web
 namespace: <namespace>
spec:
 ports:
 - port: 8080
   targetPort: 8080
   name: http
 - port: 8443
   targetPort: 8443
   name: https
 selector:
   app: frps
---
kind: Deployment
apiVersion: apps/v1
metadata:
 labels:
   app: frps
 name: frps
 namespace: <namespace>
spec:
 replicas: 1
 revisionHistoryLimit: 10
 selector:
   matchLabels:
     app: frps
 template:
   metadata:
     labels:
       app: frps
   spec:
     containers:
       - name: frps
         image: snowdreamtech/frps:latest
         imagePullPolicy: IfNotPresent
         ports:
           - containerPort: 7000
             protocol: TCP
         volumeMounts:
           - name: frps-conf
             mountPath: /etc/frp
     volumes:
       - name: frps-conf
         configMap:
           name: frps-conf
           items:
           - key: frps.ini
             path: frps.ini

Ingress配置为:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  generation: 1
  name: sharework
  namespace: <namespace>
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - <your-domain>
    secretName: <tls-secret>
  rules:
  - host: <your-domain>
    http:
      paths:
      - path: /local
        pathType: Prefix
        backend:
          service:
            name: frps-web
            port:
              number: 8080
---

请注意这里frps pod的<namespace>和Ingress所在的命名空间相同,如果不同,则Ingress指向的Backend Service应该改成external service。
另外,本Ingress是TLS加密的,其使用了tls-secret这个证书secret。如果不想使用证书,也可以将tls这一段声明去掉。

附:创建tls secret的命令为:
kubectl create secret tls tls-secret --key privkey.pem --cert fullchain.pem

本地K8S集群中部署frpc和测试web服务

本地K8S集群部署了frpc容器,并通过此容器将本地集群的Web服务合并到远程Ingress下。相关配置要点如下:

  • frpc进行站点匹配时,包含了url的全部域名和路径。比如,Ingress中使用/local指向本地K8S集群的服务,但是frpc的匹配路径locations中还是要包含/local这个部分;
  • frpc的local_ip取巧直接使用了服务名,这是因为在集群内部,访问服务名相当于访问http://svcname.namespace.svc.cluster.local(FQDN,全限定域名)。经测试是可行的,如果sample-web和frpc不在同一命名空间,应当填写FQDN;

相关配置如下:

apiVersion: v1
kind: ConfigMap
metadata:
  name: frpc-configurations
  labels:
    module: frpc
    app: frpc
data:
  frpc-conf: |-
    [common]
    server_addr = <frps server ip>
    server_port = 32890
    token = <token>
    [test-web]
    type = http
    custom_domains = <your domain>
    locations = /local/sample
    local_ip = sample-web
    local_port = 80
---
kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    module: frpc
    app: frpc
  name: frpc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frpc
  template:
    metadata:
      labels:
        module: frpc
        app: frpc
    spec:
      containers:
        - name: frpc
          image: snowdreamtech/frpc:0.42.0
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: frpc-conf
            mountPath: /etc/frp
            readOnly: true
      volumes:
        - name: frpc-conf
          configMap:
            name: frpc-configurations
            items:
            - key: frpc-conf
              path: frpc.ini
---              
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-configurations
  labels:
    module: frpc
    app: sample-web
data:
  nginx-conf: |-
    server {
        listen 80;
        server_name  localhost <your domain>;
        location /local/sample {
            alias   /usr/share/nginx/html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }    
---
kind: Service
apiVersion: v1
metadata:
  labels:
    module: frpc
    app: sample-web
  name: sample-web
spec:
  ports:
  - port: 80
    targetPort: 80
    name: http
  selector:
    app: sample-web
---
kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    module: frpc
    app: sample-web
  name: sample-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-web
  template:
    metadata:
      labels:
        module: frpc
        app: sample-web
    spec:
      containers:
        - name: web
          image: nginx:alpine
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: nginx-conf
            mountPath: /etc/nginx/conf.d/
            readOnly: true
      volumes:
        - name: nginx-conf
          configMap:
            name: nginx-configurations
            items:
            - key: nginx-conf
              path: default.conf

部署成功后,即可以通过https://your.domain/local/sample访问本地sample-web的内容。