在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
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の設定

在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端口已开放。

VPN客户端的设置
最后是VPN客户端的设置。使用Pritunl来创建服务器、组织和用户,可以下载ovpn文件。将其导入VPN客户端,但由于Pritunl服务器的私有地址,需要指定域名以便达到负载均衡器。
...
remote your.domain.name 1999 tcp-client
...
通过以上措施,应该能够连接成功。
结束
对我个人来说,StatefulSet和使用Docker容器内的网络设置(iptables)是我第一次接触,这是一个很好的主题。
如果有人知道如何同时使用TCP/UDP的方法,请务必告诉我,因为本次GCP负载平衡器的限制无法使用UDP。