系列专栏声明:比较流水,主要是写一些踩坑的点,和实践中与文档差距较大的地方的思考。这个专栏的典型特征可能是 次佳实践,争取能在大量的最佳实践中生存。

一、为什么不用 Minikube 了

用了一段时间的 Minikube,主要遇到两个问题:一是非正常重启后启动不起来,没有查出来是哪里的问题,感觉是什么资源被锁死了,非正常停机没有清理干净;二是空耗内存太多了,原本的想法是大部分应用跑在 Serverless 上所以无所谓,但实际用下来会发现还是需要一部分常驻应用的。

架构图基本和上篇是一致的,暂时先不画了,等有空再补。

二、安装 K3s

很简单的,直接一行就装好了,官方也提供了墙内源。注意这个 --docker 参数,因为我需要 Host 上跑一些额外的不方便弄进 k8s 的东西,所以指定复用 Host 上的 docker。

在用阿里云 Packer 安装时遇到了一个问题,默认用的 cgroup v1 而不是 v2,所以会报错,但不会强退,可以直接忽略。

$ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.26.1+k3s1 INSTALL_K3S_MIRROR=cn sh -s - --docker

默认使用 root 启动的,看上去 --rootless 模式 还在实验中,所以也没有深究。即 kubectl 命令要用 sudo kubectl,这点和 minikube 不一样。

三、使用内置的 Traefik 作为 Ingress

K3s 会直接绑定 Host 的 80 和 443 端口,没有找到相关的配置去关闭或者修改,所以要改变之前把 Host Traefik 挡在最前面的架构设计,改成让 K3s Traefik 接管外部请求。INSTALL_K3S_EXEC="server --disable traefik" 是另外的设计,并没有深究。

因此之前用 Host Traefik 解 *.example.com 的泛域名证书的方案不再适用了。需要改成每个 App 自己的 Ingress 去管理自己的证书 app01.example.com。注意安装参数里面有一个 --tls-san 是用来处理 k8s server 和 agent 之间请求的,不是用来处理业务请求的,所以不用管它。

首先安装 cert-manager,它会使用自己的 namespace cert-manager:

$ curl -L "https://github.com/cert-manager/cert-manager/releases/download/v1.12.7/cert-manager.yaml" -o cert-manager.yaml
$ kubectl apply -f cert-manager.yaml

建一个 cluster-issuer.yaml,注意是建在 default namespace 下的,或者说和你的业务 App 建在一起:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: username@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          ingressTemplate:
            metadata:
              annotations:
                kubernetes.io/ingress.class: traefik
$ kubectl apply -f cluster-issuer.yaml

启动你的业务 App:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: blog
  name: blog
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: blog
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  sessionAffinity: None
status:
  loadBalancer: {}

---

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  labels:
    app: blog
  name: blog
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: blog
  template:
    metadata:
      labels:
        app: blog
    spec:
      containers:
      - image: registry.example.com/orgname/blog:1.0.0
        imagePullPolicy: IfNotPresent
        name: blog
        resources: {}
      restartPolicy: Always

准备一个强制 http 跳转到 https 的 Traefik Middleware,注意也要放在 default namespace:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: http-redirect-to-https
  namespace: default
spec:
  redirectScheme:
    scheme: https
    permanent: true

最后设置 Certificate 和 Ingress:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: blog
  namespace: default
spec:
  secretName: blog
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  duration: 2160h
  renewBefore: 72h
  dnsNames:
    - blog.example.com # 这里没有泛域名了

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "traefik"
    kubernetes.io/tls-acme: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-http-redirect-to-https@kubernetescrd
    # 注意这里的写法 {namespace}-{middlewareName}@kubernetescrd
spec:
  rules:
    - host: blog.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blog
                port:
                  number: 80
  tls:
  - hosts:
    - blog.example.com
    secretName: blog

然后 cert-manager 会自己去调用 letsencrypt 的 http01 challenge 获取证书,并保存在 default namespace 的 SecretCertificate 中。

收工,https://blog.example.com

三、访问 Host 上的服务

文档 上说可以用 host.k3d.internal,试了一下不行,改成直接用 IP 10.42.0.1 可以,没有深究。

参考文献

  1. K3s的traefik配置http强制跳转https
  2. k3s redirect http to https

理斯特
18 声望9 粉丝

web/mobile/iot, front/back, js/java