自动化部署与 GitOps:Kratos 微服务框架的应用实践
什么是Ops, GitOps又是什么?
Ops(Operations)
Ops,即运维(Operations),是指管理和维护信息技术系统的活动,确保这些系统能够高效、可靠地运行。Ops涵盖了多个方面,包括但不限于:
- 系统管理:安装、配置和维护服务器、网络设备等硬件设施。
- 软件管理:安装、配置和更新操作系统、中间件、应用程序等软件。
- 监控与日志:实时监控系统性能和健康状况,收集日志信息以便故障排查。
- 备份与恢复:定期备份重要数据,制定灾难恢复计划。
- 安全管理:实施安全策略,保护系统免受攻击和威胁。
- 性能优化:优化系统性能,确保高效运行。
- 自动化:通过脚本和工具自动化日常任务,提高工作效率。
GitOps
GitOps是一种将DevOps最佳实践应用于基础设施和应用程序部署的方法论。它的核心理念是通过版本控制系统(通常是Git)来管理和自动化整个软件开发生命周期中的基础设施配置和应用程序部署过程。GitOps的核心原则包括:
- 声明式系统配置:所有基础设施和应用程序的状态都以声明式的方式定义,并存储在版本控制系统中。这意味着可以通过代码来描述期望的系统状态,而不是通过手动操作。
- 持续集成/持续部署(CI/CD):使用CI/CD流水线自动化构建、测试和部署流程。每次代码提交都会触发自动化测试和部署,确保快速、可靠地交付新功能。
- 版本控制:所有变更都通过版本控制系统进行管理,提供完整的变更历史记录,便于审计和回滚。
- 自动化:通过自动化工具和脚本,减少手动操作,提高部署速度和可靠性。
- 自我修复:系统能够自动检测和修复不一致的状态,确保实际状态与期望状态一致。
GitOps vs 传统Ops
传统Ops:
- 手动操作:许多任务需要手动执行,容易出错且效率低下。
- 缺乏版本控制:基础设施和应用程序的状态变化可能没有完整的历史记录,难以回滚。
- 依赖脚本:虽然可以使用脚本自动化某些任务,但管理和维护这些脚本本身也是一项挑战。
- 团队协作困难:开发和运维团队之间可能存在沟通障碍,导致部署流程不顺畅。
GitOps:
- 自动化:通过CI/CD流水线自动化整个部署流程,减少手动操作。
- 版本控制:所有变更都通过版本控制系统管理,提供完整的变更历史记录,便于审计和回滚。
- 声明式配置:使用声明式配置文件描述期望的系统状态,确保一致性和可预测性。
- 团队协作:开发和运维团队可以更好地协同工作,共同管理和维护系统。
- 自我修复:系统能够自动检测和修复不一致的状态,确保高可用性和稳定性。
GitHub Actions 介绍
GitHub Actions 是 GitHub 提供的一种 CI/CD 工具,允许用户在代码仓库中定义工作流(workflow),并在特定事件(如推送代码、创建标签等)发生时自动触发。
在本文中,我们将探讨以下 GitHub Actions 工作流配置:
name: Deploy backend application and swagger document to production
on:
push:
branches:
- pre
tags:
- 'v*.*.*'
字段解析:
name: 工作流的名称,用于标识不同的工作流。on: 指定工作流触发的事件。这里配置为当代码推送到pre分支或创建符合v*.*.*标签时触发。
jobs:
backend-test:
runs-on: ubuntu-latest
strategy:
matrix:
service: [ user, product ] # 定义要并行执行的服务
defaults:
run:
shell: bash
working-directory: ${{ matrix.service }}
字段解析:
jobs: 定义一个或多个并行执行的任务。在此,我们为每个微服务(如user和product)定义了一个backend-test作业。runs-on: 指定运行此作业的操作系统环境(例如 Ubuntu 最新版)。strategy.matrix: 在matrix中定义了并行执行的服务,GitHub Actions 会为每个服务并行执行后续步骤。
工作流的好处:
- 自动化测试与构建:通过 GitHub Actions 可以自动化运行测试、构建项目、创建镜像等操作,减少手动操作。
- 持续集成:在每次推送代码时,GitHub Actions 会自动触发工作流,保证代码质量和应用稳定性。
5. 什么是 Kustomize?
Kustomize 是一个用于管理 Kubernetes 配置的工具,它允许用户以一种声明式的方式对 Kubernetes 配置进行自定义,而无需重复编写配置文件。通过 Kustomize,可以在不同的环境之间共享配置并进行差异化管理。
Kustomize 带来的优势:
- 环境差异管理:Kustomize 允许用户根据不同环境(如开发、测试、生产等)进行配置差异化处理。
- 重用性:可以创建基础模板,并根据不同需求进行修改,避免重复代码。
在 GitOps 流程中,Kustomize 可以使得不同环境的配置文件更加简洁,并便于在多环境间共享资源。
2. Kubernetes 部署设计
2.1 Deployment 配置
设计思路:
- Deployment 资源负责定义应用的部署方式,包括容器的镜像、端口以及副本数等。通过将 HTTP 服务与 gRPC 服务分别暴露在不同的端口上(
30001和30002),可以清晰地区分两种不同类型的请求。HTTP 适用于传统的 RESTful API,而 gRPC 则提供更高效的远程调用方式。 imagePullPolicy: Always确保每次部署时都会拉取最新的容器镜像,避免使用旧的版本。- 通过
replicas: 1设置为一个副本,可以确保在开发和测试环境中快速验证部署,但在生产环境中可以根据需要调整为多个副本,以提高高可用性和扩展性。
apiVersion: apps/v1
kind: Deployment
metadata:
name: e-commence-user
labels:
app: e-commence-user
spec:
replicas: 1
selector:
matchLabels:
app: e-commence-user
template:
metadata:
name: e-commence-user
labels:
app: e-commence-user
spec:
containers:
- name: e-commence-user
image: example
imagePullPolicy: Always
ports:
- containerPort: 30001
protocol: TCP
name: http-server
- containerPort: 30002
protocol: TCP
name: grpc-server
restartPolicy: Always
2.2 Service 配置
设计思路:
- Service 资源用于暴露部署的容器应用,使得外部流量可以访问该服务。通过配置
NodePort类型的服务,可以让 Kubernetes 集群的每个节点暴露出指定的端口(如30001和30002)供外部访问。 - 每个服务端口通过
targetPort映射到容器内的相应端口,确保请求能够被正确地路由到容器中的服务。
apiVersion: v1
kind: Service
metadata:
name: e-commence-user-service
spec:
selector:
app: e-commence-user
ports:
- name: http
port: 30001
protocol: TCP
targetPort: 30001
nodePort: 30001
- name: grpc
port: 30002
protocol: TCP
targetPort: 30002
nodePort: 30002
type: NodePort
2.3 Namespace 配置
设计思路:
- Namespace 允许将 Kubernetes 集群中的资源分隔开来,不同的应用或团队可以使用不同的命名空间,避免资源冲突。在该项目中,我们使用了
tiktok命名空间来隔离 TikTok 电商项目相关的所有资源。
apiVersion: v1
kind: Namespace
metadata:
name: tiktok
2.4 Kustomize 配置
设计思路:
- Kustomize 是一种 Kubernetes 配置管理工具,它能够使得配置文件更加灵活,特别适合多环境管理。我们通过 Kustomize 管理不同环境下的配置差异,如生产环境、开发环境和测试环境。
patches部分用来修改基础配置(如副本数、资源限制、环境变量等),确保每个环境的需求得到满足。通过 Kustomize,我们能够避免重复的配置,并能轻松实现配置的重用与管理。
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
namespace: tiktok
patches:
- path: replica_count.yaml
- path: limits.yaml
- path: add-env.yaml
- path: ingress-http-cors-patch.yaml
- path: ingress-grpc-cors-patch.yaml
3. Ingress 配置
设计思路:
- Ingress 资源用于定义如何通过 HTTP/HTTPS 路由外部请求到集群内的服务。在该配置中,使用了多个 CORS 配置,允许特定的域名进行跨域请求。这对于前端应用和后端服务的跨域通信至关重要。
- 负载均衡策略
least_conn确保请求分发到当前连接数最少的后端,避免某些节点过载。 - 请求重试策略
proxy-next-upstream确保当请求失败时能够自动重试,提升系统的稳定性和容错能力。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: e-commence-user-grpc-ingress
annotations:
higress.io/force-ssl-redirect: "true"
higress.io/load-balance: "least_conn"
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://web.api-r.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
nginx.ingress.kubernetes.io/cors-expose-headers: "Content-Length, X-Kuma-Revision"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
nginx.ingress.kubernetes.io/cors-max-age: "86400"
higress.io/proxy-next-upstream-tries: "3"
higress.io/proxy-next-upstream-timeout: "4"
higress.io/proxy-next-upstream: "http_502,non_idempotent"
GitOps 流程:
设计思路: 自动化部署 Golang 后端应用和 Swagger 文档到生产环境。整个流程包括从代码推送到版本控制仓库,到构建 Docker 镜像、推送到容器注册表,最终通过 Kubernetes 将服务部署到生产环境。
1. 触发条件与环境变量
触发条件 (on 部分)
on:
push:
branches:
- pre
tags:
- 'v*.*.*'
该部分定义了 GitHub Actions 流水线的触发条件。它会在以下情况下触发:
- 当
pre分支有代码推送时(通常是发布前的测试分支)。 - 当一个符合
v*.*.*格式的版本标签被推送时(通常用于发布版本)。
环境变量 (env 部分)
这部分定义了多个全局环境变量,便于在多个步骤之间共享:
SERVICE_DIR:指定微服务的目录,类似于服务的根目录。REGISTER_NAMESPACE:容器镜像在容器注册表中的命名空间。VERSION:应用的版本号,默认是v1.0.0,也可以通过 Git 标签动态获取。GOOS,GOARCH,TARGET_PLATFORMS等用于设置构建时的目标操作系统和架构。GO_IMAGE,GO_PROXY:设置 Golang 镜像和代理。DEPLOY_TIMEOUT:设置部署超时时间。BACKEND_NAMESPACE:指定后端应用的 Kubernetes 命名空间。BACKEND_HTTP_PORT和BACKEND_GRPC_PORT:后端服务的 HTTP 和 gRPC 端口。
2. 步骤
2.1 后端服务测试:backend-test
任务运行在 ubuntu-latest 环境上,并且通过矩阵策略并行执行多个服务(如 user 和 product)。执行以下步骤:
- 检出代码:使用
actions/checkout@v4检出 Git 仓库中的代码。 - 设置 Go 环境:使用
actions/setup-go@v5配置 Go 版本,并缓存 Go 依赖项。 - 安装迁移工具:安装
migrate工具,用于数据库迁移。 - 运行数据库迁移:根据服务目录的存在性运行数据库迁移命令(如
make migrate-up)。这是为了确保数据库结构与应用的版本兼容。 - 运行 Go 单元测试:执行
go test命令进行单元测试,并生成覆盖率报告。-short参数确保跳过长时间运行的测试。
2.2 构建后端镜像:backend-build
运行在 ubuntu-latest 环境上,并通过矩阵策略并行执行多个服务。主要步骤如下:
- 检出代码:与上一步一样,使用
actions/checkout@v4检出代码。 - 设置 QEMU 仿真器:通过
docker/setup-qemu-action@v3配置 QEMU 仿真器,允许在 x86 架构上构建和测试 ARM 等其他架构的 Docker 镜像。 - 缓存依赖项:使用
actions/cache@v3缓存 Go 的依赖项,避免每次都重新下载依赖,提高构建速度。 - 登录容器注册表:登录到容器注册表,以便可以推送镜像。
- 构建、标记并推送镜像:使用 Docker 构建后端服务镜像,并将其推送到容器注册表。这里会根据环境变量构建镜像,例如版本号、Go 环境配置、HTTP/GRPC 端口等。
2.3 部署到 Kubernetes:backend-deploy
将构建好的 Docker 镜像部署到 Kubernetes 集群。具体步骤如下:
- 检出代码:同样检出代码,以便可以访问配置文件和 Kustomize 工具。
- 设置 Kustomize:下载并安装
kustomize,它是 Kubernetes 的配置管理工具,可以用来组合和管理不同的 Kubernetes 配置文件。 - 安装 kubectl:下载并安装
kubectl,它是 Kubernetes 的命令行工具,用于与 Kubernetes 集群交互。 - 连接 Kubernetes 集群:通过
KUBE_CONF环境变量中的 Kubernetes 配置信息,连接到 Kubernetes 集群。 - 创建或更新 Kubernetes 密钥:通过
kubectl创建或更新存储数据库和 Redis 连接信息的密钥(如DB_SOURCE,REDIS_ADDRESS等)。这确保了在 Kubernetes 中部署时,可以正确地访问数据库和 Redis 服务。 - 更新镜像并部署:使用 Kustomize 更新部署配置中的镜像版本号(从容器注册表获取),并通过
kubectl apply部署到 Kubernetes 集群。kubectl rollout status用于检查部署是否成功,若失败则进行回滚。
优点:
- 增强的可靠性与一致性
- 测试阶段确保了每个提交都经过了基本的单元测试和数据库迁移验证,确保代码质量和数据库兼容性。
- 构建与部署通过 Docker 镜像实现了服务的隔离性,保证了开发、测试和生产环境中的一致性。
- 可维护性与扩展性
- 矩阵策略:通过并行化执行多个服务(如
user和product),提高了流水线的效率,同时保证了不同服务的构建和部署能独立进行。 - Kustomize 配置管理:通过 Kustomize 管理 Kubernetes 配置文件,使得不同环境下的部署更加灵活和易于维护。可以方便地为生产、开发、测试等环境配置不同的参数。
- 整体优化 通过缓存依赖项和 Docker 镜像,可以减少每次构建的时间,并提升持续集成的效率。通过精细化的步骤管理和容器化部署,提升了服务的可扩展性和弹性。
最终的yml文件如下:
name: Deploy backend application and swagger document to production
run-name: Deploy backend application and swagger document to production
on:
push:
branches:
- pre
tags:
- 'v*.*.*'
env:
SERVICE_DIR: user
REGISTER_NAMESPACE: e-commence
VERSION: v1.0.0
GOOS: linux
GOARCH: amd64
TARGET_PLATFORMS: linux/amd64
GO_IMAGE: golang:1.23.3-alpine3.20
GO_PROXY: https://proxy.golang.org
CGO_ENABLED: 0
DEPLOY_TIMEOUT: 30s
BACKEND_NAMESPACE: tiktok
BACKEND_HTTP_PORT: 30001
BACKEND_GRPC_PORT: 30002
jobs:
backend-test:
runs-on: ubuntu-latest
strategy:
matrix:
service: [ user, product ]
defaults:
run:
shell: bash
working-directory: ${{ matrix.service }}
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- '5432:5432'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
check-latest: true
cache-dependency-path: '**/go.sum'
- name: Install migrate
run: |
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.1/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/bin/
which migrate
- name: Run database migrate
run: |
export DB_SOURCE="postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
if [ -d ${{ matrix.service }} ]; then
echo "Migration directory exists. Running migrations..."
make migrate-up
else
echo "Migration directory does not exist. Skipping migrations."
fi
- name: Go test
run: |
go test -short -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
backend-build:
needs: backend-test
name: Build image
runs-on: ubuntu-latest
strategy:
matrix:
service: [ user, product ]
defaults:
run:
shell: bash
working-directory: ${{ matrix.service }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-modules-
- name: Login Cloud Registry
run: echo ${{ secrets.REGISTRY_PASSWORD }} | docker login ${{ secrets.REGISTRY }} --username ${{ secrets.REGISTRY_USERNAME }} --password-stdin
- name: Build tag and push image to Cloud Registry
run: |
ls ./
docker buildx build . \
-t actions/${{ matrix.service }} \
--build-arg GO_PROXY=$GO_PROXY \
--build-arg GOIMAGE=$GO_IMAGE \
--build-arg CGOENABLED=$CGO_ENABLED \
--build-arg VERSION=$VERSION \
--build-arg HTTP_PORT=$BACKEND_HTTP_PORT \
--build-arg GRPC_PORT=$BACKEND_GRPC_PORT \
--build-arg GOOS=$GOOS \
--build-arg GOARCH=$GOARCH \
--platform linux/amd64
docker tag actions/${{ matrix.service }} ${{ secrets.REGISTRY }}/${REGISTER_NAMESPACE}/${{ matrix.service }}:$VERSION
docker push ${{ secrets.REGISTRY }}/${REGISTER_NAMESPACE}/${{ matrix.service }}:$VERSION
backend-deploy:
needs: backend-build
runs-on: ubuntu-latest
strategy:
matrix:
service: [ user, product ]
defaults:
run:
shell: bash
working-directory: ${{ matrix.service }}/manifests/application/overlays/production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Kustomize
run: |
curl -o kustomize --location https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64
chmod u+x ./kustomize
./kustomize version
- name: Install kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl{,.sha256}"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
chmod +x ./kubectl
- name: Connect Kubernetes Cluster
run: |
mkdir -pv ~/.kube/
echo "${{secrets.KUBE_CONF}}" > ~/.kube/config
chmod 600 ~/.kube/config
- name: Deploy
run: |
if ./kubectl get ns $BACKEND_NAMESPACE; then
echo "namespaces $BACKEND_NAMESPACE already exists, skip create"
else
./kubectl create ns $BACKEND_NAMESPACE
fi
./kubectl config set-context --current --namespace $BACKEND_NAMESPACE
./kubectl delete secret db-source-secret || true
./kubectl create secret generic db-source-secret --from-literal='DB_SOURCE=${{ secrets.DB_SOURCE }}'
./kubectl delete secret redis-address-secret || true
./kubectl create secret generic redis-address-secret --from-literal='REDIS_ADDRESS=${{ secrets.REDIS_ADDRESS }}'
./kubectl delete secret redis-username-secret || true
./kubectl create secret generic redis-username-secret --from-literal='REDIS_USERNAME=${{ secrets.REDIS_USERNAME }}'
./kubectl delete secret redis-password-secret || true
./kubectl create secret generic redis-password-secret --from-literal='REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}'
./kustomize edit set image example=${{ secrets.REGISTRY }}/${REGISTER_NAMESPACE}/${{ matrix.service }}:$VERSION
./kustomize build . | kubectl apply -f -
./kubectl rollout status deploy || ./kubectl rollout undo deploy
./kubectl get po -owide
./kubectl get svc
6. 在 GitOps 中使用 Kustomize
以下是一个 Kustomize 的示例配置:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
- ingress-http.yaml
- ingress-grpc.yaml
namespace: tiktok
字段解析:
resources: 定义了 Kustomize 需要引用的资源文件。namespace: 指定 Kubernetes 中的命名空间。
在不同环境中使用 Overlays:
apiVersion: apps/v1
kind: Deployment
metadata:
name: e-commence-user
spec:
template:
spec:
containers:
- name: e-commence-user
env:
- name: DB_SOURCE
valueFrom:
secretKeyRef:
key: DB_SOURCE
name: db-source-secret
字段解析:
env: 用于在容器中注入环境变量,结合 Kubernetes Secret 进行敏感数据的注入。
7. 结合 Kustomize 和 GitOps
GitOps 中结合 Kustomize 可以带来更高的灵活性和可扩展性。通过 Kustomize 可以为每个环境创建不同的 overlay(覆盖层),确保每个环境的配置保持一致,同时避免重复代码。
例如,通过以下命令:
./kustomize build . | kubectl apply -f -
这行命令通过 Kustomize 处理配置文件并应用到 Kubernetes 集群中。GitOps的流程结束