在Kubernetes(GKE)平台上建立一个高可用的Pritunl服务器

不管远程工作对此有什么影响,因为需要建立VPN服务器,所以需要记录步骤。

根据我的调查,我没有找到涵盖可用性和SSL设置的Pritunl文章,因此我认为这对想在实际环境中使用Pritunl的人可能会很有参考价值。

Pritunl:https://github.com/pritunl/pritunl/releases/tag/1.29.2423.17

环境

    GKE: 1.14.10-gke.27
    Pritunl: 1.29.2423.17
    Docker Container Base Image: Ubuntu 18.04
    MongoDB: 4.2

虽然实例类型可以很小,但根据需求,网络速度可能成为瓶颈。

整体形象

本次我们将在GKE上部署Pritunl服务器。
Pritunl使用一个用于配置管理的Web UI和用于VPN连接的端口,因此我们将使用GCP的L4负载均衡器。因此,负载均衡器将在Pritunl服务器端处理HTTPS,而不是在负载均衡器端处理。幸运的是,Pritunl本身具有Let’s Encrypt的证书管理机制,因此我们将利用它。

另外,Pritunl选择了MongoDB作为其数据库,由于在GCP上没有类似的托管MongoDB集群服务,因此我们还将在Kubernetes上构建MongoDB集群。

总结起来,大致需要以下步骤。

    K8s上へMongoDBクラスタを構築
    PritunlのDocker Imageを作成
    K8s上へ複数のPritunl Podを構築し、L4ロードバランサーを置く
    DNS設定とSSL化
    VPN Clientの設定
Screen Shot 2563-05-03 at 18.22.41.png

在K8s上建立MongoDB集群。

主要根据这篇文章来说,但为了加入一些关于GKE的内容,我会进行一些解释。

存储类

首先,我们准备一个用于存储MongoDB持久化数据的存储库。
在GKE中,默认情况下已定义了名为standard的HDD StorageClass。在这里,我们将直接使用它,但如果想要使用SSD或更改复制选项,可以自定义定义。

无头服务

接下来,我们会设置内部DNS以便从Kubernetes内部(本例中是Pritunl)访问MongoDB集群。

在连接MongoDB时,我们需要知道哪个节点是Primary Node,因此希望能够为每个节点分配独立的DNS,而不使用负载均衡器等。另外,当创建StatefulSet时,每个Pod都会被分配一个唯一的名称(如mongo-statefulset-0)。
因此,我们可以使用Headless Service来将这些Pod的名称分配为DNS名称。

yaml 的设定是将 clusterIP 设置为 None。

apiVersion: v1
kind: Service
metadata:
  name: mongo-svc
spec:
  ports:
    - port: 27017
      targetPort: 27017
  clusterIP: None
  selector:
    app: mongo

这样一来,对于名为mongo-statefulset-0的Pod,将分配一个名为mongo-statefulset-0.mongo-svc的DNS名称。

MongoDB StatefulSet可以进行重述为:MongoDB有状态集合。

最后我们将创建 MongoDB 的 Pods 。

DB的Pod与普通的无状态Pod不同,

    それぞれ固有の識別子を持つ
    それぞれ固有のStorageを持つ
    rolling updateやterminateは、一定の順序で行う必要がある

由于具有这些特点,我们使用StatefulSet。在这里使用我们之前定义的存储类(Storage Class)。

这个yaml文件看起来是这样的。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo-statefulset
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mongo
  serviceName: "mongo-service"  # 1
  template:
    metadata:
      labels:
        app: mongo
    spec:
      terminationGracePeriodSeconds: 10
      containers:
        - name: mongo
          image: mongo:4.2
          resources:
            requests:
              memory: "200Mi"
          command:
            - mongod
            - "--replSet"
            - rs0
            - "--bind_ip"
            - "0.0.0.0"
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: mongo-persistent-storage  # 2
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        name: mongo-persistent-storage  # 3
        annotations:
          storageClassName: "standard"  # 4
      spec:
        accessModes:
          - ReadWriteOnce  # 5
        resources:
          requests:
            storage: 30Gi

不考虑从名字推测的因素,

在serviceName中指定之前的Headless Service。

根据https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#statefulsetspec-v1-apps阅读,为了在创建Pod同时具有唯一的DNS名称,需要先创建Service并指定它。

3. 通过在2中引用预先准备的用于持久化的卷,即使相关的Pod崩溃,也可以重新挂载并保持状态。
4. 引用所准备的StorageClass。本次将使用最初在GKE中定义的standard作为每台机器30GB的准备形式。
5. 关于5,似乎是为了使其他Pod也能够挂载相同的磁盘,但在能够构建大多数集群配置的数据库系统中,我认为没有必要使用其他设置。

另外,关于刚才提到的Update Storategy,请注意,默认情况下.spec.updateStrategy.type = RollingUpdate,即在更新时,Pod将按顺序逐个更新。

将MongoDB进行集群化

在GKE上部署StatefulSet后,将启动具有类似mongo-statefulset-0.mongo-svc之类的DNS的Pod。然而,它们还不能组成集群,因此需要进入其中一台Pod,并使用client命令进行集群化。

kubectl exec -it mongo-statefulset-0 -- /bin/bash

mongo >
rs.initiate( {
   _id : "rs0",
   members: [
      { _id: 0, host: "mongo-statefulset-0.mongo-svc:27017" },
      { _id: 1, host: "mongo-statefulset-1.mongo-svc:27017" },
      { _id: 2, host: "mongo-statefulset-2.mongo-svc:27017" }
   ]
})

我在GKE上成功搭建了MongoDB集群。由于此次仅从k8s集群内部进行连接,所以并未设置密码等信息。

创建Pritunl的Docker镜像

由于公式未发布Docker镜像,因此我将从Ubuntu 18.04的镜像自行构建。

FROM ubuntu:18.04

RUN apt update
COPY --chown=root:root ["install.sh", "/root"]
RUN bash /root/install.sh

COPY start.sh /root/

EXPOSE 80
EXPOSE 443
EXPOSE 1999

CMD ["bash", "/root/start.sh"]
set -ex

RUN apt install -y gnupg2
echo 'deb http://repo.pritunl.com/stable/apt bionic main' > /etc/apt/sources.list.d/pritunl.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv 7568D9BB55FF9E5287D586017AE645C0CF8E292A
apt update
apt install -y pritunl iptables
set -ex

pritunl set-mongodb mongodb://mongo-statefulset-0.mongo-svc,mongo-statefulset-1.mongo-svc,mongo-statefulset-2.mongo-svc/pritunl?replicaSet=rs0
pritunl set app.redirect_server true
pritunl set app.server_ssl true
pritunl set app.server_port 443
pritunl set app.reverse_proxy true
pritunl start

在Pritunl服务器启动之前,我需要动态配置一些mongoDB的设置等内容,因此我准备了一个启动脚本。尽管此处我将DNS直接编写在脚本中,但根据需要,您可以将其放置在环境变量中。

由于没有进行特定优化且缺乏通用性,因此没有解释。

在K8s上构建多个Pritunl Pod,并放置L4负载均衡器。

创建Pritunl的部署

首先,将创建的Pod部署到k8s中。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pritunl-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pritunl
  template:
    metadata:
      labels:
        app: pritunl
    spec:
      containers:
        - name: pritunl-container
          image: asia.gcr.io/<project name>/pritnul:1
          securityContext:
            privileged: true
          ports:
            - containerPort: 80
            - containerPort: 443
            - containerPort: 1999

在这里我们讨论的是,Pritunl只需要设置securityContext来修改iptables。其他都是普通的部署。

当部署成功后,请将密码作为初始设置更改。
通过使用kubectl exec进入Pod,使用pritunl reset-password命令更改密码。

L4负载均衡器

我们将提前准备静态IP。在这里,由于L4负载均衡器将在区域级别部署,因此我们将在与GKE集群相同的区域获取静态IP(此次使用IPv4)。

apiVersion: v1
kind: Service
metadata:
  name: pritunl-lb-service
spec:
  selector:
    app: pritunl
  ports:
    - protocol: TCP
      name: "1999"
      port: 1999
      targetPort: 1999
    - protocol: TCP
      name: "80"
      port: 80
      targetPort: 80
    - protocol: TCP
      name: "443"
      port: 443
      targetPort: 443
  type: LoadBalancer
  loadBalancerIP: "<your static IP>"

在这里,我们预留给Web UI和VPN客户端使用的端口。如果您要设置多个VPN,需要分别保留端口。

另外,根据我了解,GCP的L4负载均衡器似乎无法同时配置TCP和UDP,因此本次我们选择了使用TCP作为VPN客户端的传输协议。

DNS配置和SSL加密

到目前为止,我们已经可以使用静态IP访问Pritunl服务器群。然而,由于使用了L4负载均衡器,所以无法进行SSL加密。让我们最后进行该设置。

首先进行DNS设置。只需要将您的域名的A记录设置为静态IP即可。

如果设置了这个选项,您可以在浏览器中访问仍未获得证书的Web UI,并在登录后输入域名以使用Let’s Encrypt获取SSL证书。在等待一段时间后,Pritunl会自动为您获取SSL证书,同时确保80端口已开放。

Screen Shot 2563-05-03 at 22.33.21.png

VPN客户端的设置

最后是VPN客户端的设置。使用Pritunl来创建服务器、组织和用户,可以下载ovpn文件。将其导入VPN客户端,但由于Pritunl服务器的私有地址,需要指定域名以便达到负载均衡器。

...
remote your.domain.name 1999 tcp-client
...

通过以上措施,应该能够连接成功。

结束

对我个人来说,StatefulSet和使用Docker容器内的网络设置(iptables)是我第一次接触,这是一个很好的主题。

如果有人知道如何同时使用TCP/UDP的方法,请务必告诉我,因为本次GCP负载平衡器的限制无法使用UDP。

bannerAds