部署 Rust 应用到 k8s 集群

k3s 是轻量级的 K8s,比较适合中小型的项目,生产可用。这篇文档描述了使用 k3s 的一些生产实践。

架构

对于任何技术来说,首先从宏观层面来了解其架是非常必要的。我们先从 k3s 的架构说起:

整体架构分为两部分,k3s Server 和 k3s Agent:

k3s Server 由如下组建构成:

  • Kine:它是随着 k3s 一起发布的,兼容 Etcd v3 协议,用于屏蔽底层数据库之间的差异、统一转发的职责,作为数据库抽象层使用。默认情况下,使用 Sqlite;

  • API Server:认证(Authentication)、鉴权(Authorization)以及准入(Admission,Mutating & Valiating),提供 REST API,这是 Control Panl 中回一个可由用户访问 API 进行交互的组件,提供其他模块之间的数据交互和通讯的枢纽。

  • Controller Manager:用来管理各种 Controller,Watch 状态的变更,对比期望状态(Desired State)与实际状态(Actual State),通过创建、更新、删除资源来推动集群往期望状态靠拢。

    • 是多个控制器的组合,每个 Controller 事实上都是一个 Control loop,负责监听其管控的对象,当每个对象发生变更时完成配置;

    • 如果配置失败会触发自动重试,整个集群会在控制器不断重试机制下确保最终一致性(Eventual Consistency)。

  • Scheduler: 调度器会监控新建的 pods,并将其分配给合适的节点;

    • 它是一个特殊的 Controller,工作原理和其他 Controller 是一样的;

    • 它的职责是监控集群中的所有没有调度的 Pod,并且获取当前集群中所有节点的健康状态和资源使用晴空,为调度 Pod 选择最佳计算节点,完成调度;

    • 分为三个阶段:Predict阶段过滤不满足业务需求的节点,比如资源不足、端口冲突等。Priority:按既定要素将满足调度需求的节点评分,选择最佳节点。Bind将计算节点和 Pod 进行绑定,完成调度。

k3s Agent 由如下组建构建:

  • Kubelet:节点中 Pod 的生命周期的管理,执行任务并将 Pod 的状态报告给主节点的渠道,通过容器运行时来运行容器,并进行健康监测;

  • Flannel:轻量级的容器网络插件,为集群中的 Pod 提供扁平化的覆盖网络;

其他的一些组件:

  • Etcd:它是 CoreOS 基于 Raft 开发的分布式 key-value 存储系统,可用于服务发现、共享配置以及一致性保障(比如说数据库选主、分布式锁等)。

安装

K3s 不同于 K8s,最先感受到的就是安装部署上的简单快速,单机环境下,就一行命令搞定:

## 关闭防火墙
$ systemctl disable firewalld --now
## 安装
$ curl -sfL https://get.k3s.io | sh -

在网络顺畅的情况下,一分钟内可以搞定。然后运行下面的命令查看节点,如果正常输出,则表示安装成功:

$ kubectl get node
NAME   STATUS   ROLES                  AGE   VERSION
k3s    Ready    control-plane,master   98s   v1.32.3+k3s1

全局 TLS 证书管理

首先我们需要安装 Cert-Manager,用作全局证书管理。包括,下文中,我们需要构建私有的 Registry,也需要绑定 TLS 证书。

1. 安装 Cert-Manager

Cert-Manager 可以用来管理 k3s 集群中自动化所有的 TLS。 应用下面的配置,最新稳定版的版本号,在 GitHub Releases 页面查看:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml

然后通过下面的命令,验证是否安装成功:

$ kubectl -n cert-manager get pods
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-6468fc8f56-6jpd2             1/1     Running   0          3m22s
cert-manager-cainjector-7fd85dcc7-8kcrm   1/1     Running   0          3m22s
cert-manager-webhook-57df45f686-vphs8     1/1     Running   0          3m22s

2. 创建 ClusterIssuer

Cert-Manager 的作用是定义做什么,而 ClusterIssuer 的作用则是定义如何做,用哪一家 CA、有哪些参数,并且它是集群级别的,所有的命令空间都可以使用。

接着创建 Let’s Encrypt ClusterIssuer,创建文件 cluster-issuer.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: happy.hacking.icu@gmail.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: traefik

Traefik 是现代化的云原生边缘代理(Edge Proxy)和 Kubernerts Ingress Controller,主要功能包括反向代理(Reverse Proxy)、负载均衡(Load Balancing)、动态服务发现(Discovery)、内置了 Let’s Encrypt ACME 证书管理、支持重定向、限流、熔断、身份验证等中间件(Middleware)。在 k3s 中自动启用。

然后应用配置:

$ kubectl apply -f cluster-issuer.yaml
clusterissuer.cert-manager.io/letsencrypt-prod created

启用 Registry

安装后,我们首先启用镜像仓库,并且使用 Let’ Encrypt 来申请 SSL 证书。在开始之前,先确保已经做好了域名解析。

1. 部署 Registry

接着部署 Registry, 创建文件 registry-deployment.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: registry

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
  template:
    metadata:
      labels:
        app: registry
    spec:
      containers:
      - name: registry
        image: registry:2
        ports:
        - containerPort: 5000
        volumeMounts:
        - name: data
          mountPath: /var/lib/registry
      volumes:
      - name: data
        hostPath:
          path: /opt/registry
          type: DirectoryOrCreate

---
apiVersion: v1
kind: Service
metadata:
  name: registry-svc
  namespace: registry
spec:
  type: ClusterIP
  ports:
  - port: 5000
    targetPort: 5000
  selector:
    app: registry

然后应用配置:

$ kubectl apply -f registry-deployment.yaml
namespace/registry created
deployment.apps/registry created
service/registry-svc create

2. 绑定证书

接着创建 Ingress 并绑定 Let’s Encrypt 证书,创建文件 registry-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: registry-ingress
  namespace: registry
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  tls:
  - hosts:
    - <你的域名>
    secretName: registry-tls
  rules:
  - host: <你的域名>
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: registry-svc
            port:
              number: 5000

然后应用配置:

$ kubectl apply -f registry-ingress.yaml
ingress.networking.k8s.io/registry-ingress created

3. 配置私有仓库

配置 k3s 使用私有的仓库, 创建或编译 /etc/rancher/k3s/registries.yaml

mirrors:
  "registry.k3s.testing.icu":
    endpoint:
      - "https://registry.k3s.testing.icu"
configs:
  "registry.k3s.teting.icu":
    tls:
      insecure_skip_verify: false

然后重启 k3s 的服务:

systemctl restart k3s

然后就可以通过访问 https://<你的域名>/v2/_catalog 来验证是否生效了。

4. 启用鉴权

如果是在公网上访问,那么就必须添加一层鉴权,防止资源可以公开访问。

首先,生成 htpasswd 文件:

# 确保安装了: dnf install httpd-tools
$ mkdir /opt/auth
$ htpasswd -Bbn <用户名> <密码> >> /opt/auth/htpasswd
# 随后会要求输入密码

接着,把 htpasswd 存到 Kubernetes Secret:

$ kubectl create secret generic registry-htpasswd \
    --from-file=htpasswd=/opt/auth/htpasswd \
    -n registry
secret/registry-htpasswd created

修改 registry-deployment.yaml 配置文件,添加 Auth 相关配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: registry
spec:
  template:
    spec:
      containers:
      - name: registry
        image: registry:2
        env:
          - name: REGISTRY_AUTH
            value: "htpasswd"
          - name: REGISTRY_AUTH_HTPASSWD_REALM
            value: "Registry Realm"
          - name: REGISTRY_AUTH_HTPASSWD_PATH
            value: "/auth/htpasswd"
        volumeMounts:
          - name: auth
            mountPath: /auth
            readOnly: true
      volumes:
        - name: auth
          secret:
            secretName: registry-htpasswd

然后应用配置:

$ kubectl apply -f registry-deployment.yaml

然后可以通过 docker login <域名> 来验证是否可以 Login Succeeded。

Hello World

接下来,通过一个 Hello World 的示例来展示如何将一个 Web 应用通过 GitHub CI 集成到 k3s 集群,并对外提供服务。

1. 编写代码

这个项目以来 Tokio 和 Axum 两个框架,依赖如下:

[dependencies]
tokio ={ version = "1.44.2", features = ["full"] }
axum = "0.8.4"

项目代码如下:

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app: Router = Router::new().route("/", get(|| async { "Hello World" }));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:80").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

然后本地可以通过 cargo run 来测试,访问 http://127.0.0.1 测试是否输出 Hello World

2. 编写 Dockerfile

然后为这个项目编写 Dockerfile 来创建镜像:

FROM rust:1.86-bullseye AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bullseye-slim AS runtime
WORKDIR /app
COPY --from=builder /app/target/release/app /app/app

RUN apt-get update \
 && apt-get install -y --no-install-recommends ca-certificates \
 && rm -rf /var/lib/apt/lists/*

EXPOSE 80
CMD ["/app/app"]

3. 发布镜像

接下来,要发布镜像到我们之前创建的 Registry 中:

docker build -t k3s-rust-demo:1.0.2 .
docker tag app:1.0.0 <registry-domain>/app:1.0.2
docker push <registry-domain>/app:1.0.2

4. 创建命名空间

接着,我们要部署应用到集群中了。首先创建命名空间:

apiVersion: v1
kind: Namespace
metadata:
    name: k3s-rust-demo

应用配置文件:

$ kubectl apply -f namespace.yaml

5. 创建部署清单

接着编写 deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
    name: k3s-rust-demo
    labels:
        app: k3s-rust-demo
spec:
    replicas: 2
    selector:
        matchLabels:
            app: k3s-rust-demo
    template:
        metadata:
            labels:
                app: k3s-rust-demo
        spec:
            imagePullSecrets:
                - name: regcred
            containers:
                - name: k3s-rust-demo
                  image: <registry-domain>/k3s-rust-demo:0.1.2
                  imagePullPolicy: IfNotPresent
                  ports:
                    - containerPort: 8000
                  resources:
                    requests:
                      cpu: "100m"
                      memory: "128Mi"
                    limits:
                      cpu: "1000m"
                      memory: "512Mi"

应用配置:

$ kubectl apply -f deployment.yaml -n k3s-rust-demo

6. 创建服务

接着创建 service 配置文件 service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: k3s-rust-demo-svc
  namespace: k3s-rust-demo
spec:
  type: ClusterIP
  selector:
    app: k3s-rust-demo
  ports:
    - port: 80
      targetPort: 8000

应用配置:

$ kubectl apply -f service.yaml -n k3s-rust-demo

7. 对外提供服务

最后,对外提供 Web 服务,并且管理 SSL 证书:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: k3s-rust-demo-ingress
  namespace: k3s-rust-demo
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"

    # Traefik 启用 HTTPS 重定向
    traefik.ingress.kubernetes.io/entrypoints: "websecure"
    traefik.ingress.kubernetes.io/redirect-entrypoint: "websecure"
spec:
  ingressClassName: "traefik"
  tls:
    - hosts:
        - rust.demo.testing.icu
      secretName: k3s-rust-demo-tls
  rules:
    - host: rust.demo.testing.icu
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: k3s-rust-demo-svc
                port:
                  number: 80

应用配置:

$ kubectl apply -f ingress-with-tls.yaml -n k3s-rust-demo

完成之后,可以通过域名访问你的服务了。

GitHub CI

接下来就可以集成 GitHub CI 了,让这一切都自动发生。CI 整个过程如下:

检出代码 -> 安装依赖 -> 缓存依赖 -> 发布镜像 -> 设置 kube 凭证 -> 滚动更新

接下来就按照这个过程,分步编写 GitHub CI 的配置文件。你可以先在项目根目录下创建配置文件:.github/workflows/main.yaml

1. 获取 Kube 配置

首先要获取 Kube 配置,作为使用在 GitHub CI 服务器上使用 kubectl 操作集群的认证凭证。

# 将配置扁平化、最小化后通过 base64 编码
kubectl config view --raw --flatten --minify | base64 -w0

这里需要注意,默认输出的 config 中的 server 指向 127.0.0.1:6443, 这个在 CI 中是无法访问的。你需要将其替换成公网可以访问的 IP 地址。可以先将配置输出到临时文件,修改后再 base64 编码即可。

然后注入到 GitHub 仓库的 Secrets 中去,位于仓库的 Settings->Security->Secrets and variables->Actions 中:

之后就可以在 CI 的配置文件中安全地使用这个变量了。

2. 构建并发布镜像

接着我们就来编写 GitHub CI 的配置了,编辑之前创建.github/workflows/main.yaml

name: Deploy to prod

on:
  push:
    branches:
      - main

上面的配置是给这次 CI 命名,然后指出当 main 分支发生 push 的时候,触发 CI。接下来定义 jobs, 首先需要构建和发布镜像,配置如下:

jobs:
  build-and-push:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.tag.outputs.IMAGE_TAG }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Determine image tag
        id: tag
        run: |
          # 取前 8 位提交 SHA 作为镜像标签
          echo "IMAGE_TAG=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT

      - name: Log in to Docker registry
        uses: docker/login-action@v2
        with:
          registry: ${{ secrets.DOCKER_REGISTRY }}
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build & push image
        uses: docker/build-push-action@v3
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: |
            ${{ secrets.DOCKER_REGISTRY }}/k3s-rust-demo:${{ steps.tag.outputs.IMAGE_TAG }}

这个脚本涉及三个 secrets,也需要提前在 GitHub 中配置,分别是:

  • DOCKER_REGISTRY:私有仓库的域名
  • DOCKER_USERNAME:登录私有仓库的用户名
  • DOCKER_PASSWORD:登录私有仓库的密码

利用之前已经编写好了 Dockerfile 分阶段编译,打包后的大小仅 80MB 左右,然后构建镜像发布到私有的仓库中。

3. 滚动更新应用

最后,使用 kubectl 命令,远程滚动更新应用即可,这一步的配置如下:

deploy:
    name: Deploy to k3s
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.27.0'

      - name: Configure kubeconfig
        run: |
          mkdir -p $HOME/.kube
          echo "${{ secrets.KUBE_CONFIG_DATA }}" | base64 --decode > $HOME/.kube/config
          chmod 600 $HOME/.kube/config

      - name: Update Deployment image
        run: |
          kubectl -n k3s-rust-demo set image deployment/k3s-rust-demo \
            k3s-rust-demo="${{ secrets.DOCKER_REGISTRY }}/k3s-rust-demo:${{ needs.build-and-push.outputs.image-tag }}"
          kubectl -n k3s-rust-demo rollout status deployment/k3s-rust-demo

总结

这篇文档我们演示了如何极速部署 k3s 的集群(演示的是单机环境,无高可用),然后通过一个简单的 Hello World 的例子,演示了将一个 Rust 应用部署到 k3s 的应用中。最后通过 GitHub CI 将应用发不到 k3s 集群中。

通过这篇应用,可以让大家对 k3s/k8s 有一个具体的认识。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!