微服务框架 Kratos 之部署微服务
Kratos在部署微服务的时候需要一个额外的参数:
- -conf: 指向配置文件的目录
所以在部署kratos服务时, 需要注意, 添加参数, 另外kratos的main入门在cmd/<server>/main.go,在build时使用go build ./...不像普通的go build, 这也是一个注意的地方. 然后就是kratos它支持GRPC, 你的服务可能会起HTTP与GRPC的端口
Kratos 与 Web库 Gin 的区别
Kratos 和 Gin 是两个流行的 Go 语言微服务框架,但它们的设计目标和适用场景有所不同。
-
Kratos:
- 全栈框架:Kratos 是一个全栈的微服务框架,提供了从服务发现、负载均衡、API 网关、认证授权、限流熔断、日志监控等全方位的支持。
- 模块化设计:Kratos 采用了模块化设计,开发者可以根据需要选择和组合不同的模块,灵活构建微服务。
- 协议支持:Kratos 支持多种协议,包括 HTTP、gRPC、Dubbo 等,适用于复杂的微服务架构。
- 社区支持:Kratos 由字节跳动开源,拥有活跃的社区和丰富的文档资源。
-
Gin:
- 轻量级框架:Gin 是一个轻量级的 Web 框架,专注于快速开发高性能的 HTTP 服务。
- 简单易用:Gin 提供了简洁的 API 和中间件机制,适合快速开发简单的 RESTful API。
- 性能优越:Gin 在性能方面表现出色,适合高并发场景。
- 社区支持:Gin 拥有庞大的用户群体和丰富的第三方插件生态。
部署方式
常见的部署有三种:
- 二进制
- Docker
- Kubernetes
这里不讲二进制部署, 着重讲解Docker 和 Kubernetes
Docker 和 Kubernetes
Docker:
- 容器化技术:Docker 是一种轻量级的虚拟化技术,允许开发者将应用程序及其依赖打包成容器,确保应用程序在不同环境中的一致性。
- 隔离性和可移植性:Docker 容器提供了良好的隔离性和可移植性,可以在任何支持 Docker 的平台上运行。
- 开发和测试:Docker 适用于开发和测试环境,可以快速构建和运行应用程序。
Kubernetes:
- 容器编排平台:Kubernetes 是一个开源的容器编排平台,用于自动化容器化应用程序的部署、扩展和管理。
- 高可用性和扩展性:Kubernetes 提供了高可用性和自动伸缩能力,确保应用程序的稳定运行。
- 服务发现和负载均衡:Kubernetes 内置了服务发现和负载均衡机制,简化了微服务之间的通信。
- 滚动更新和回滚:Kubernetes 支持滚动更新和回滚,确保应用程序的平滑升级。
Docker 和 Kubernetes 部署的区别
Docker 部署:
- 单节点部署:Docker 部署通常在一个节点上运行容器,适用于小型应用或开发测试环境。
- 手动管理:需要手动管理容器的启动、停止和监控。
- 简单配置:使用 Docker Compose 文件可以轻松管理多个容器的配置。
Kubernetes 部署:
- 多节点集群:Kubernetes 部署在一个多节点集群上运行容器,适用于生产环境。
- 自动管理:Kubernetes 自动管理容器的生命周期,包括自动重启、负载均衡和故障恢复。
- 复杂配置:使用 Kubernetes 配置文件(如 YAML)管理集群和服务,配置较为复杂。
如何选型想必读者已然心里有数, 下一篇文章讲解Kubernetes
Dockerfile 实践
我们将从一个可能的普通的Dockerfile 开始,逐步优化到最终的最佳实践 Dockerfile。
普通的 Dockerfile
网上搜索了一个go的Dockerfile, 内容如下:
dockerfile
# 打包依赖阶段使用 golang 作为基础镜像
FROM golang:1.14 as builder
# 启用 go module
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct
WORKDIR /app
COPY . .
# 指定 OS 等,并 go build
RUN GOOS=linux GOARCH=amd64 go build .
# 由于我不止依赖二进制文件,还依赖 views 文件夹下的 html 文件还有 assets 文件夹下的一些静态文件
# 所以我将这些文件放到了 publish 文件夹
RUN mkdir publish && cp toc-generator publish && \
cp -r views publish && cp -r assets publish
# 运行阶段指定 scratch 作为基础镜像
FROM alpine
WORKDIR /app
# 将上一个阶段 publish 文件夹下的所有文件复制进来
COPY --from=builder /app/publish .
# 指定运行时环境变量
ENV GIN_MODE=release \
PORT=80
EXPOSE 80
ENTRYPOINT ["./toc-generator"]
这个Dockerfile乍一看没什么问题, 但是你仔细分析, 它的缺点有以下几点:
-
依赖下载未分离:
- 在构建阶段,所有的文件(包括源代码和依赖文件)都被复制到容器中,然后再进行构建。这样会导致每次构建时都需要重新下载依赖文件,增加了构建时间。
-
缓存利用率低:
- 由于依赖下载和源代码复制在一个步骤中完成,Docker 缓存机制无法充分利用。每次源代码发生变化时,都会重新下载依赖文件。
-
缺少详细的构建信息:
- 构建命令
go build .没有提供详细的构建信息,如版本号、构建时间等,这在调试和版本控制中可能会带来不便。
- 构建命令
-
安全性和权限管理:
- 使用
scratch作为基础镜像虽然体积小,但缺乏必要的系统工具和安全特性。使用alpine作为基础镜像时,没有创建非特权用户来运行应用程序,存在安全隐患。
- 使用
-
环境变量设置不完整:
- 虽然设置了
GIN_MODE和PORT,但可能还需要设置其他环境变量,如数据库连接字符串、Redis 地址等。
- 虽然设置了
-
日志和监控支持不足:
- 没有配置日志和监控相关的工具和环境变量,这对于生产环境来说是一个重要的缺失。
优化后的Dockerfile
# syntax=docker/dockerfile:1
# https://docs.docker.com/go/dockerfile-reference/
# 定义基础镜像的 Golang 版本
ARG GOIMAGE=golang:1.23.3-alpine3.20
FROM --platform=$BUILDPLATFORM ${GOIMAGE} AS build
WORKDIR /src
# 版本号
ARG VERSION=latest
# Go 的环境变量, 例如 alpine 镜像不内置 gcc, 则关闭 CGO 很有效
ARG GOOS=linux
ARG GOARCH=amd64
ARG CGOENABLED=0
# Go 的环境变量, 例如 alpine 镜像不内置 gcc, 则关闭 CGO 很有效
ARG GO_PROXY=https://proxy.golang.com.cn,direct
COPY . .
# 设置环境变量
RUN go env -w GOPROXY=$GO_PROXY
# 利用 Docker 层缓存机制,单独下载依赖项,提高后续构建速度。
# 使用缓存挂载和绑定挂载技术,避免不必要的文件复制到容器中。
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
# 获取代码版本号,用于编译时标记二进制文件
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
GOOS=$GOOS \
GOARCH=$GOARCH \
CGOENABLED=$CGOENABLED \
go build -o /bin ./...
COPY ./configs /bin/configs
FROM alpine:latest AS final
# 从构建阶段复制编译好的 Go 应用程序到运行阶段
COPY --from=build /bin/user /bin/
COPY --from=build /bin/configs /bin/configs
# 用户进程 ID
ARG UID=10001
# 后端程序的 HTTP/gRPC 端口
ARG HTTP_PORT=30001
ARG GRPC_PORT=30002
# 修改镜像源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# 安装应用运行必需的系统证书和时区数据包
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
# 指定容器对外暴露的端口号
EXPOSE $HTTP_PORT $GRPC_PORT
VOLUME /data/conf
# 设置容器启动时执行的命令
CMD ["/bin/user", "-conf", "/data/conf"]
它有如下优化:
-
多阶段构建:
- 构建阶段:使用 Golang 镜像构建应用程序,生成二进制文件。
- 运行阶段:使用 Alpine 镜像作为最终运行环境,减少镜像大小。
-
环境变量:
- 使用
ARG和ENV指令传递和设置构建和运行时的环境变量,确保灵活性和可配置性。
- 使用
-
缓存和绑定挂载:
- 使用
--mount指令利用 Docker 层缓存机制,避免不必要的文件复制,提高构建速度。
- 使用
-
安全性和性能:
- 关闭 CGO (
CGOENABLED=0),减少依赖,提高安全性和性能。 - 创建非特权用户运行应用程序,增强容器安全性。
- 关闭 CGO (
-
多架构支持:
- 使用
docker buildx构建多架构的二进制文件,确保在不同平台上的兼容性。
- 使用
Docker构建
单平台构建
构建Docker所属的当前平台与架构的二进制文件
VERSION=dev
REPOSITORY="team/backend"
Docker 容器在 Linux 内核上运行,即便是在 macOS 或 Windows 环境中。
如果使用docker构建时传递 GOOS=darwin 会导致构建的二进制文件不兼容于 Linux 环境,从而出现 exec format error
所以在使用docker构建时的目标平台的GOOS应该为 linux,而非 darwin。
docker build . \
--progress=plain \
-t dev/app:dev \
--build-arg CGOENABLED=0 \
--build-arg GOIMAGE=golang:1.23.3-alpine3.20 \
--build-arg GOOS=linux \
--build-arg GOARCH=arm64 \
--build-arg VERSION=$VERSION \
--build-arg HTTP_PORT=30001 \
--build-arg GRPC_PORT=30002
多架构支持
构建多架构的二进制文件, 如果是Docker Desktop, 需要在Docker Desktop 启用 containerd 映像存储
VERSION=v1.0.0
REPOSITORY="tiktok/orders"
GOOS=linux
GOARCH=amd64
HTTP_PORT=30001
GRPC_PORT=30002
PLATFORM_1=linux/amd64
docker buildx build . \
--progress=plain \
-t $REPOSITORY:$VERSION \
--build-arg CGOENABLED=0 \
--build-arg GOIMAGE=golang:1.23.3-alpine3.20 \
--build-arg VERSION=$VERSION \
--build-arg HTTP_PORT=$PORT \
--build-arg GRPC_PORT=$PORT \
--build-arg GOOS=$GOOS \
--build-arg GOARCH=$GOARCH \
--platform $PLATFORM_1 \
--load
推送
register="myrepo.com"
docker tag $REPOSITORY:$VERSION $register/$REPOSITORY:$VERSION
docker push $register/$REPOSITORY:$VERSION
拉取
GOOS=linux
GOARCH=amd64
docker pull $register/$REPOSITORY:$VERSION --platform $GOOS/GOARCH
运行
docker run \
--rm \
-p 30001:30001 \
-p 30002:30002 \
-e GIN_MODE=release \
-e DB_SOURCE="postgresql://postgres:postgres@localhost:5432/simple_bank?sslmode=disable" \
$REPOSITORY:$VERSION
使用Docker compose 来优化 Docker 命令行
使用命令行来构建和运行并不是一个流行的推荐, 它有几个缺点:
1. 可维护性和一致性
- 手动操作容易出错:手动输入命令容易出错,尤其是在复杂的构建和部署过程中。
- 缺乏版本控制:命令行操作通常不会记录在版本控制系统中,难以追踪和回溯。
- 配置不一致:不同开发人员或团队成员可能使用不同的命令和参数,导致配置不一致。
2. 自动化和集成
- 缺乏自动化:手动构建和运行镜像不适合持续集成/持续交付(CI/CD)流程,难以实现自动化。
- 依赖管理:手动管理依赖关系和构建顺序复杂且容易出错。
3. 可扩展性和管理
- 难以管理多服务:对于多个微服务,手动管理每个服务的构建和运行非常繁琐。
- 资源管理和调度:手动操作难以实现资源管理和调度,特别是在多节点集群环境中。
4. 环境一致性
- 环境差异:手动操作可能导致开发、测试和生产环境之间的差异,影响应用程序的稳定性和可靠性。
- 依赖环境:手动构建和运行依赖于特定的开发环境,难以在不同环境中复现。
5. 安全性
- 权限管理:手动操作难以确保权限管理的一致性和安全性。
- 审计和日志:手动操作缺乏详细的审计和日志记录,难以追踪和排查问题。
利用Docker compose 来优化 Dockerfile, 让我们把一个docker run改造一下:
docker run \
--rm \
-p 30001:30001 \
-p 30002:30002 \
-e GIN_MODE=release \
-e DB_SOURCE="postgresql://postgres:postgres@localhost:5432/simple_bank?sslmode=disable" \
$REPOSITORY:$VERSION
变成:
services:
users:
image: tiktok/users:prod
platform: linux/amd64
ports:
- "30001:30001"
- "30002:30002"
container_name: users
restart: always
networks:
- app-network
environment:
- DB_SOURCE=postgresql://postgres:postgres@postgres17:5432/simple_bank?sslmode=disable
- REDIS_ADDRESS=redis7:6379
command:
- "/bin/user"
- "-conf"
- "/bin/configs"
逐行解释:
-
服务定义:
services: -
服务名称:
users:- 定义一个名为
users的服务。
- 定义一个名为
-
镜像:
image: tiktok/users:prod- 指定要使用的 Docker 镜像。这里使用的是
tiktok/users:prod镜像,标签为prod,表示生产环境的镜像。
- 指定要使用的 Docker 镜像。这里使用的是
-
平台:
platform: linux/amd64- 指定目标平台。这里指定为
linux/amd64,表示该镜像是为 x86_64 架构的 Linux 系统构建的。
- 指定目标平台。这里指定为
-
端口映射:
ports: - "30001:30001" - "30002:30002"- 将宿主机的端口
30001和30002分别映射到容器内的端口30001和30002。这使得容器内的服务可以通过宿主机的这些端口访问。
- 将宿主机的端口
-
容器名称:
container_name: users- 指定容器的名称为
users。这有助于在 Docker 命令中更方便地引用该容器。
- 指定容器的名称为
-
重启策略:
restart: always- 设置容器的重启策略为
always,表示无论容器因何原因退出,Docker 都会自动重启该容器。
- 设置容器的重启策略为
-
环境变量:
yaml 深色版本 environment: - DB_SOURCE=postgresql://postgres:postgres@postgres17:5432/simple_bank?sslmode=disable - REDIS_ADDRESS=redis7:6379-
设置容器的环境变量。这里设置了两个环境变量:
DB_SOURCE:指定数据库连接字符串,用于连接 PostgreSQL 数据库。REDIS_ADDRESS:指定 Redis 服务器的地址。
-
-
命令:
yaml 深色版本 command: - "/bin/user" - "-conf" - "/bin/configs"- 指定容器启动时执行的命令。这里启动
/bin/user可执行文件,并传入-conf和/bin/configs参数
- 指定容器启动时执行的命令。这里启动
最后, 启动应用:
docker compose up