发布于 2026-01-06 5 阅读
0

使用 Traefik、Let's Encrypt 和 Zookeeper 运行个人云;使用 Traefik HTTPS 和 Let's Encrypt 的 Kubernetes Ingress;GKE 抢占式节点,打造您专属的混沌猴子;使用 Zookeeper 为 Traefik 共享键值存储;所有 Kubernetes YAML 设置文件;最终成果;关于我

使用 Traefik、Let's Encrypt 和 Zookeeper 运行个人云

使用 Traefik 的 Kubernetes 入口

HTTPS 和 Let's Encrypt

GKE抢占式节点,你自己的混沌猴子

Traefik 与 Zookeeper 共享 K/V 商店

所有用于设置的 Kubernetes YAML 文件

最终结果

关于我

使用 Traefik 的 Kubernetes 入口

正如我在上一篇博客文章中提到的,我希望专注于构建一个与供应商无关的云平台,尽可能使用不依赖于任何云服务的技术。

虽然谷歌云默认提供负载均衡的 HTTP 入口,但与运行小型节点相比,它显然非常昂贵,而且我只听说过使用 Traefik 作为 Kubernetes 入口的良好口碑。

我按照Manuel 的优秀指南设置了 Traefik ,并做了一些小的修改(最终文件可以在文章末尾找到)。

HTTPS 和 Let's Encrypt

Traefik 内置了对使用Let's Encrypt自动获取和续订 HTTPS 证书的支持。由于 HTTPS 是最佳实践,而且也是 HTTP2 和 PWA 的必要条件,所以我按照Traefik 文档中的示例配置进行了设置。

因为我只使用了一个 Traefik 节点,所以我选择使用本地 acme.json 文件进行简单设置,该文件在节点运行时存储证书。

GKE抢占式节点,你自己的混沌猴子

为了节省成本,我选择使用“抢占式虚拟机”作为节点来运行我在 GKE 上的 Kubernetes 集群。根据谷歌的文档:“抢占式虚拟机是 Google Compute Engine 虚拟机实例,其最长运行时间为 24 小时,且不提供任何可用性保证。” 这意味着我的 Kubernetes 集群中的节点会随机宕机,并且永远不会运行超过 24 小时。虽然这显然不是生产环境的明智之举,但我还是选择接受它,并将节点宕机视为一种“混沌猴子”,它迫使我编写具有弹性的代码。

我遇到的一个具体例子是:Let's Encrypt 生产 API 对同一 URL 的证书请求次数有限制,每周最多只能请求 5 个。由于我最初的配置比较简单,没有保存证书,所以每次 Traefik 节点终止时,证书都会丢失。虽然 Traefik 在每次启动时都能顺利地重新生成证书……但五次启动后,我就达到了请求次数限制,并收到了一个关于缺少证书的不安全警告。

Traefik 与 Zookeeper 共享 K/V 商店

为 Traefik 添加一个共享的键值存储。如果要在集群模式下运行 Traefik,则必须使用键值存储(而且我认为我的配置很容易扩展)。这也意味着我可以将生成的证书存储在键值存储中,这样 Traefik 重启后证书就不会再丢失了。

由于我之前使用过 Zookeeper,而且设置过程也比较简单,所以我选择了它。

所有用于设置的 Kubernetes YAML 文件

最后是博文的重点,我的完整配置以 yaml 文件的形式呈现,您可以直接将其部署到您的 GKE 集群中:

首先设置 Zookeeper。

参考这份优秀的资源:https://github.com/kow3ns/kubernetes-zookeeper/blob/master/manifests/README.md

apiVersion: v1
kind: Service
metadata:
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zk
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: zk
spec:
  serviceName: zk-hs
  replicas: 1
  podManagementPolicy: Parallel
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: zk
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"
      containers:
      - name: kubernetes-zookeeper
        imagePullPolicy: Always
        image: "gcr.io/google_containers/kubernetes-zookeeper:1.0-3.4.10"
        resources:
          requests:
            memory: "200M"
            cpu: "0.3"
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        command:
        - sh
        - -c
        - "start-zookeeper \
          --servers=1 \
          --data_dir=/var/lib/zookeeper/data \
          --data_log_dir=/var/lib/zookeeper/data/log \
          --conf_dir=/opt/zookeeper/conf \
          --client_port=2181 \
          --election_port=3888 \
          --server_port=2888 \
          --tick_time=2000 \
          --init_limit=10 \
          --sync_limit=5 \
          --heap=512M \
          --max_client_cnxns=60 \
          --snap_retain_count=3 \
          --purge_interval=12 \
          --max_session_timeout=40000 \
          --min_session_timeout=4000 \
          --log_level=INFO"
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/zookeeper
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
Enter fullscreen mode Exit fullscreen mode

Traefik 的权限

# create Traefik cluster role
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
 name: traefik-ingress-controller
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
---
# create Traefik service account
kind: ServiceAccount
apiVersion: v1
metadata:
  name: traefik-ingress-controller
  namespace: default
---
# bind role with service account
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
  name: traefik-ingress-controller
  namespace: default
Enter fullscreen mode Exit fullscreen mode

Traefik 配置

请注意 Zookeeper 的配置,它使用了“客户端服务”(cs) 的服务地址,以及 Let's Encrypt 的配置。

# define Traefik configuration
kind: ConfigMap
apiVersion: v1
metadata:
  name: traefik-config
data:
  traefik.toml: |
    # traefik.toml
    defaultEntryPoints = ["http", "https"]
    [entryPoints]
      [entryPoints.http]
        address = ":80"
        [entryPoints.http.redirect]
          entryPoint = "https"
      [entryPoints.https]
      address = ":443"
        [entryPoints.https.tls]

      [zookeeper]
        endpoint = "zk-cs.default.svc.cluster.local:2181"
        watch = true
        prefix = "traefik"

      [acme]
      email = "your@email.com"
      storage = "traefik/acme/account"
      onHostRule = true
      caServer = "https://acme-v02.api.letsencrypt.org/directory"
      acmeLogging = true
      entryPoint = "https"
        [acme.httpChallenge]
        entryPoint = "http"

      [[acme.domains]]
        main = "your.domain.com"
Enter fullscreen mode Exit fullscreen mode

Traefik 的部署

为了节省开发成本,我只运行了一个副本,但我也将其扩展到了三个,以测试即使随机节点宕机,它是否也能始终保持 100% 的运行状态,结果一切正常 :)。

# declare Traefik deployment
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: traefik-ingress-controller
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: traefik-ingress-controller
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      volumes:
        - name: config
          configMap:
            name: traefik-config
      containers:
      - name: traefik
        image: "traefik:1.7.14"
        volumeMounts:
          - mountPath: "/etc/traefik/config"
            name: config
        args:
        - --configfile=/etc/traefik/config/traefik.toml
        - --kubernetes
        - --logLevel=INFO
Enter fullscreen mode Exit fullscreen mode

Traefik 服务

# Declare Traefik ingress service
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-controller
spec:
  selector:
    app: traefik-ingress-controller
  ports:
    - port: 80
      name: http
    - port: 443
      name: tls
  type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

最终结果

使用 Traefik 和 Zookeeper 的最终工作负载

Traefik 和 Zookeeper 的工作负载

还有 Kubernetes Ingress(请忽略我在此用作演示的应用程序)。

Kubernetes 入口

关于我

我是一名全栈开发人员和数字产品爱好者,目前可以承接自由职业工作,并且一直在寻找下一个令人兴奋的项目 :)。

您可以通过https://heltweg.org在线联系我

文章来源:https://dev.to/rhanarion/run-a-personal-cloud-with-traefik-let-s-encrypt-and-zookeeper-4a7g