k3s + Gitea Action 实现 CI/CD 流程

后端3 min
#运维#k3s#Gitea
目录(5)

k3s + Gitea Action 实现 CI/CD 流程

环境介绍

本文在 Win11 下通过 Docker 部署 k3s 集群 + runner ,使用内网 Gitea 作为代码仓库,最终通过 Gitea Actions 实现 CI/CD 流程。

其他环境如 Mac OS, linux 大同小异,可以参考思路。

k3s 集群部署

安装 k3s

k3s 本身支持 Linux、CentOS、Ubuntu 等环境,可以直接通过指南或者脚本意见安装,详见 k3s - 安装要求

笔者的运行环境是 Win11,因此需要借助 k3d 进行安装。

注: 在用 k3d 之前,笔者也尝试过 Win11 + Wsl2 + Ubuntu,在实际使用中,遇到了非常令人头痛的网络问题。 后续不得已放弃,兴趣的朋友可以挑战下...

k3d

k3d 是一个借助 docker 运行 k3s 集群的社区工具。这个方案不仅 Win 可以用,Mac OS 也可以。

因为笔者用的是 Win11,终端这里采用的是 Powershell(pwsh),所以下面的 shell 命令和流程均为 pwsh 相关的。

如果想看其他的操作指南,跳转这里 k3d.io - v5.8.3 (可能非最新版本噢)

安装 scoop 包管理工具

## 允许脚本执行,按 Y 确认
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

## 安装 scoop
irm get.scoop.sh | iex

## 查看版本
scoop --version

安装 k3d

## 安装 k3d
scoop install k3d

## 查看版本
k3d --version

## 安装 helm (可选)
scoop install helm

## 查看版本
helm version

创建集群

## 创建集群 mycluster 可以替换成自己的集群名称
k3d cluster create mycluster

## 查看集群状态
k3d cluster list

## 查看集群节点
kubectl get nodes

安装仪表盘(kubernetes dashboard)

kubernetes/dashboard - Github 官方仓库

部署 UI

这里借助的 helm,其他方式可以参考官方文档

## 添加 kubernetes-dashboard 仓库
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
## 使用 kubernetes-dashboard Chart 部署名为 `kubernetes-dashboard` 的 Helm Release
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard

访问 UI

暴露端口后,仍需要创建用户、生成 token 才可用使用,看下面的步骤

配置 RBAC

注 admin-user 将在仪表盘中拥有管理权限。

dashboard.admin-user.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

dashboard.admin-user-role.yml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: admin-user
    namespace: kubernetes-dashboard
## 创建 admin-user
kubectl create -f dashboard.admin-user.yml -f dashboard.admin-user-role.yml

## 创建 token
kubectl -n kubernetes-dashboard create token admin-user

配置 RBAC (合并版本)

dashboard-admin.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: admin-user
    namespace: kubernetes-dashboard
## 创建 ServiceAccount 和 ClusterRoleBinding
kubectl apply -f dashboard-admin.yml

## 创建 token
kubectl -n kubernetes-dashboard create token admin-user

部署 Nginx 实现跳过登录

  1. 生成一个时间超久的 token
kubectl -n kubernetes-dashboard create token admin-user --duration=999999h
  1. 替换 yaml 模板,进行部署

dashboard-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dashboard-nginx
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dashboard-nginx
  template:
    metadata:
      labels:
        app: dashboard-nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-conf
              mountPath: /etc/nginx/conf.d
      volumes:
        - name: nginx-conf
          configMap:
            name: dashboard-nginx-conf
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: dashboard-nginx-conf
  namespace: kubernetes-dashboard
data:
  default.conf: |
    server {
      listen 80;
      set $dashboard_token "xxxx";

      location / {
          proxy_pass https://kubernetes-dashboard-kong-proxy.kubernetes-dashboard.svc.cluster.local:443;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Authorization "Bearer $dashboard_token";
          proxy_set_header X-Skip-Auth-Redirect true; # 关键头
          proxy_ssl_verify off;
      }
    }
---
apiVersion: v1
kind: Service
metadata:
  name: dashboard-nginx
  namespace: kubernetes-dashboard
spec:
  type: NodePort
  selector:
    app: dashboard-nginx
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30000
kubectl apply -f dashboard-nginx.yaml
  1. 访问

localhost:30000

遇到的问题

  • 查看 pod 日志网络出错

    • 复现方式:

      1. 通过仪表盘 pod -> 查看日志。 提示: xxxx net/http: TLS handshake timeout
      2. 通过 kubectl logs <pod-name> 超时
    • 解决方法: 问题是 wsl + ubuntu 部署 k3s 时出现的。后续换用了 k3d,问题解决。

参考链接

Gitea Runner 部署

安装 runner

参考 k8s did 部署模式进行部署。 Kubernetes Docker in Docker Deployment with act_runner

通过 kubectl 部署

  1. 获取 Runner Registration Token

Gitea 右上角头像 -> 设置 -> 侧边栏 Actions -> Runners -> 创建 Runner -> 复制 Registration Token

更多内容看指南: 获取注册令牌 - Gitea Docs

  1. 将 token 创建为 secret

kubectl 命令方式:

kubectl create secret generic runner-secret \
--from-literal=token=<< registration token >>

yaml 部署方式:

runner-secret.yaml

---
apiVersion: v1
data:
  token: << base64 encoded registration token >>
kind: Secret
metadata:
  name: runner-secret
type: Opaque
kubectl apply -f runner-secret.yaml

注:kubectl 传入的 token 是原始值,yaml 需要写入 base64 编码后的值

  1. 部署 runner

dind-docker.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: act-runner-vol
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: local-path # template 里是 standard 咱们用的 k3s 因此需要换成 local-path
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: act-runner
  name: act-runner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: act-runner
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: act-runner
    spec:
      # hostNetwork: true # 视情况选择
      restartPolicy: Always
      volumes:
        - name: docker-certs
          emptyDir: {}
        - name: runner-data
          persistentVolumeClaim:
            claimName: act-runner-vol
        # - name: runner-config # 如果需要给 Runner 指定 config 可以使用,详见下方的 configMap
        #   configMap:
        #     name: gitea-runner-config
      containers:
        - name: runner
          image: gitea/act_runner:nightly
          command:
            [
              "sh",
              "-c",
              "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- run.sh",
            ]
          env:
            - name: DOCKER_HOST
              value: tcp://localhost:2376
            - name: DOCKER_CERT_PATH
              value: /certs/client
            - name: DOCKER_TLS_VERIFY
              value: "1"
            - name: GITEA_INSTANCE_URL
              value: http://gitea-http.gitea.svc.cluster.local:3000 # 需要替换成自己的Gitea 地址,如果部署在集群里,不需要额外变更
            - name: GITEA_RUNNER_REGISTRATION_TOKEN
              valueFrom:
                secretKeyRef:
                  name: runner-secret
                  key: token
            # - name: CONFIG_FILE # runner config
            #   value: /config/config.yaml
          volumeMounts:
            - name: docker-certs
              mountPath: /certs
            - name: runner-data
              mountPath: /data
            - name: runner-config
              mountPath: /config/config.yaml
              subPath: config.yaml
        - name: daemon
          image: docker:23.0.6-dind
          env:
            - name: DOCKER_TLS_CERTDIR
              value: /certs
          securityContext:
            privileged: true
          volumeMounts:
            - name: docker-certs
              mountPath: /certs
kubectl apply -f dind-docker.yaml
  1. (可选) Runner Config

config.example.yaml

gitea-runner-config.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: gitea-runner-config
data:
  config.yaml: |
    log:
      level: info

    runner:
      file: .runner
      capacity: 1
      envs:
        A_TEST_ENV_NAME_1: a_test_env_value_1
        A_TEST_ENV_NAME_2: a_test_env_value_2
      env_file: .env
      timeout: 3h
      shutdown_timeout: 0s
      insecure: false
      fetch_timeout: 5s
      fetch_interval: 2s
      github_mirror: ''
      labels:
        - "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
        - "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
        - "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04"

    cache:
      enabled: true
      dir: ""
      host: ""
      port: 0
      external_server: ""

    container:
      network: "host" # 指定网络
      privileged: true # 特权模式
      options: -v /var/run/docker.sock:/var/run/docker.sock # 挂载 docker 守护进程
      workdir_parent:
      valid_volumes:
      - /var/run/docker.sock # 白名单
      docker_host: ""
      force_pull: true
      force_rebuild: false
      require_docker: true
      docker_timeout: 300s

    host:
      workdir_parent:
kubectl apply -f gitea-runner-config.yaml

通过 helm chart 安装

参考 helm-actions

遇到的问题

  • Registration token 作为 secret 传入时,需要 base64 编码

  • did 挂载的act-runner-vol持久卷在 k8s 和 k3s 中表现不一致

    • k8s 中挂载类型是 storageClassName: standard
    • k3s 中挂载类型是 storageClassName: local-path

参考链接

CI/CD 流程

设置密钥

密钥管理 - Gitea Docs

根据需求设置全局密钥,还是仓库级别密钥

CI 流程

.gitea\workflows\build-and-push.yaml

name: Build and Push Docker Image

on:
  push:
    branches:
      - master # 指定分支
  workflow_dispatch:

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      # 切出分支拉取最新代码
      - name: Checkout code
        uses: actions/checkout@v4

      # 安装docker buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          driver: docker

      # 设置docker镜像仓库
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # 构建镜像
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/demo:latest

      # 显示推送的镜像详情
      - name: Show pushed image details
        run: |
          IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/demo:latest"
          echo "🎉 Image pushed: $IMAGE"
          echo "🔗 Docker Hub: https://hub.docker.com/r/${{ secrets.DOCKERHUB_USERNAME }}/demo"

CD 流程

.gitea\workflows\deploy.yaml

name: Deploy to K3s

on:
  push:
    branches:
      - master
  workflow_dispatch: # 手动触发也行

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup kubectl
        uses: azure/setup-kubectl@v4
        with:
          version: "latest" # 或指定具体版本,比如 "v1.29.0"

      - name: Configure Kubeconfig
        # 假设您的 KUBE_CONFIG 秘密是 Base64 编码的 kubeconfig 文件内容
        run: |
          # 1. 创建 ~/.kube 目录
          mkdir -p $HOME/.kube

          # 2. 将 Base64 编码的 KUBE_CONFIG 秘密解码并写入默认配置文件
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config

          # 3. 确保 kubectl 知道配置文件的位置 (可选, 但安全)
          echo "KUBECONFIG=$HOME/.kube/config" >> $GITHUB_ENV

      - name: Deploy with kubectl
        run: |
          kubectl apply -f xxxx.yaml

遇到的问题

  • actions/checkout@v4 拉去私有仓库代码卡住 网络问题,通过设置 dind-docker.yaml 里 runner hostNetwork: true 解决。 原理是:让 runner 直接使用宿主机的网络,避免使用容器的网络。(笔者的 Gitea 部署在内网)

  • did 容器里使用 kubectl 地址不对的问题 需要指定 k3s 集群配置进去。

参考链接