SpringCloud微服务部署环境参数动态设置?
SpringCloud微服务运维最佳实践,如何动态定义各种启动参数?
笔者是一名Java服务端程序员,学习微服务后,在部署时发现过程复杂,且做的重复工作非常多,因此学习基本运维。目标是,使用Kubernetes和容器技术进行微服务编排和部署。
本文只讨论服务容器化,不涉及K8s和Jenkins相关内容。
问题概述
在服务容器化时,一些参数必须动态传入,以适应不同的部署环境。变化的参数包括:
- 部署环境(开发环境、测试环境、预发环境、生成环境)
- 服务版本(0.0.1、1.0.2、1.0.1-beta)
- 服务发现(部署时将服务注册到注册中心如Nacos)
我的Java项目(Example)由多个微服务构成:如Example-core,Example-auth,Example-gateway等。以Example-auth这个微服务作为切入点,打包为Docker容器的代码为:
# 使用官方的OpenJDK 17作为基础镜像FROM openjdk:17
# 镜像环境变量:
# 开发环境:dev
# 测试环境:test
# 预发环境:staging
# 生产环境:prod, 默认生产环境
ARG ENVIRONMENT=prod
# Jar包的版本,默认0.0.1-SNAPSHOT
ARG JAR_VERSION=0.0.1-SNAPSHOT
# 注册中心服务地址
ARG SERVER_NAME=www.nacos-server.cn
# 维护者
MAINTAINER xlxing@bupt.edu.cn
# 拷贝文件到Docker容器中
COPY target/auth-${JAR_VERSION}.jar /app/auth-${JAR_VERSION}.jar
# 暴露服务端口
EXPOSE 8999
# 容器启动项
ENTRYPOINT ["java", "-jar", "/app/auth-${JAR_VERSION}.jar", "--spring.profiles.active=${ENVIRONMENT}", "--spring.cloud.nacos.discovery.ip=${SERVER_NAME}"]
在构建容器时可以动态传入参数(jar包版本为1.1.0,环境是dev,服务发现地址www.my-auth.cn,构建的容器命名为myapp/example:auth-dev-1.1.0):
docker build --build-arg JAR_VERSION=1.1.0 ENVIRONMENT=dev SERVER_NAME=www.my-auth.cn -t myapp/example:auth-dev-1.1.0 .
该内容产生了以下两个具体问题:
1. 如何动态获取JAR_VERSION
在构建项目时,仍然需要手动传入项目版本,实际上该信息存在于项目中,如该微服务auth的pom.xml文件:
<artifactId>auth</artifactId> <version>0.0.1-SNAPSHOT</version>
<name>auth</name>
<description>权限微服务</description>
理想情况是:后端程序员来定义项目的版本,而流水线只需要依赖项目即可。
但是在当前情况下:当auth项目的version变更时,构建Docker镜像的指令也需要同步变化。该问题可以总结为,构建容器时如何从项目中获取JAR_VERSION?
docker build --build-arg JAR_VERSION=1.1.0 ENVIRONMENT=dev SERVER_NAME=www.my-auth.cn -t myapp/example:auth-dev-1.1.0 .
2. 如何自动获取宿主机的SERVER_NAME
微服务启动时,将该服务注册到Nacos上,声明自己的服务地址。当部署到具体的服务器上时,该地址才被绑定,如auth服务可以部署在server-a上,也可以部署到server-b上。如何在运行时确定服务地址而不是构建时。
补充内容:
auth服务的配置文件
spring: application:
name: auth
# Nacos配置
cloud:
nacos:
server-addr: www.nacos-server.cn
# 服务发现
discovery:
cluster-name: BEIJING
# 设置为非临时实例
ephemeral: true
# 设置命名空间
namespace: e35500e1-2441-4001-b60f-3f7d55bxxxxx
# 配置中心
config:
file-extension: yaml # 文件后缀名
namespace: e35500e1-2441-4001-b60f-3f7d55bxxxxx
group: DEFAULT_GROUP
dubbo:
# 将Dubbo注册到Nacos中,这样可供消费者直接使用
application:
name: dubbo-auth
protocol:
name: dubbo
port: -1
registry:
address: nacos://www.nacos-server.cn?namespace=228df068-54b7-405e-9e32-72c759d79ed9
group: DEV_DUBBO_GROUP
拓展问题:使用K8s和容器在分布式环境下部署微服务项目最佳实践?
其中涉及到很多配置相关的问题,缺乏一个系统性的文章探讨如何部署和运维。
一些尝试:
- 我有两台服务器A和B,我在部署前已经计划将auth服务部署到B上,因此构建时参数SERVER_NAME=B,构建好的容器无法直接在其他服务器上启动。
一条可行的方法是,在构建容器时不指定SERVER_NAME,在docker run
时动态传入参数。ARG SERVER_NAME=www.nacos-server.cn
改为ENV SERVER_NAME=www.nacos-server.cn
- JAR_VERSION目前是手动更改。
第一个问题:
我们在构建开发流水线的时候,会写一个脚本(比如Jenkins)。类似于这样的一个过程:
- 从pom.xml中获取version值,记为VERSION
- 使用脚本更改Dockerfile中的JAR_VERSION,此时Dockerfile中的JAR_VERSION=${VERSION}
- 生成新的Docker Image,例如
docker build -t myapp/auth:${VERSION} .
总的来说,我们无法直接在Dockerfile中获取pom.xml中的VERSION,但是可以在流水线脚本中往Dockerfile中插入静态值。
第二个问题:
我们可以将流水线部署分为两个部分,构建和部署
你的回复没有解决部署时的环境变量问题,SERVER_NAME是与所属的服务器相关的,在使用K8s时构建的配置文件如k8s-prod.yaml中,env也是一些动态参数。使用环境变量只是解决了可以在运行时变更参数的作用。
比如k8s架构有3个子节点k8s-node01、k8s-node02、k8s-node03,在部署的时候将auth这个微服务部署到哪一个节点,对运维工程师来说是透明的。
此时想到了一个笨重的方法:在k8s-node01、k8s-node02、k8s-node03的系统中提前准备好环境变量MY_IPV4,然后在docker run
的时候参数除了在k8s的yaml文件中定义,也可以从服务器的环境变量中获取。例如,gateway将auth服务路由到lb://auth
,而lb://auth
信息是从注册中心拿到的,如http://111.229.38.208:8999
。
在传统的微服务部署中,我可以基本使用nacos作为注册中心使用。K8s中的注册中心原理是DNS服务器。我现在尝试将传统微服务半自动化部署迁移到k8s部署上来,对于服务注册与发现可能有一些理解不充分。希望前辈指正。
主要问题是:传统部署微服务时,微服务启动时总是将自身(自身所在节点的ip)注册到Nacos,如何使得微服务部署是无状态的。
spring: application:
name: gateway
cloud:
# SpringCloudGateway配置
gateway:
default-filters:
# http请求的Header中增加 k, v
- AddRequestHeader=gateway-env, gateway-dev
# 前端个请求uri会拼接一个版本前缀发送到特定的服务
routes:
- id: auth
uri: lb://auth
filters:
# 服务版本前缀
- PrefixPath=/api/v1
predicates:
- Path=/auth/**
- id: multimedia
uri: lb://multimedia
filters:
- PrefixPath=/api/v1
predicates:
- Path=/multimedia/**
- id: core
uri: lb://core
filters:
- PrefixPath=/api/v1
predicates:
- Path=/core/**
# Gateway不使用Servlet而是WebFlux
main:
web-application-type: reactive
# 微服务分布式鉴权专用Redis
# Gateway和Auth共享一个权限管理Redis数据库
data:
redis:
client-type: lettuce
database: 0
host: www.xingxiaolin.cn
port: 6379
lettuce:
pool:
enabled: true
max-active: 8
max-idle: 8
回答:
更新:
我大概理解了你的问题了。
- 你的问题应该是服务注册到nacos的时候,需要把实例的IP端口信息提交上去。问题出在每次部署的时候你需要手动指定这个上报的IP端口,比较麻烦。
这里可能会有问题:
- gateway在转发请求到其他服务时,应该走“内网”传输。也就是服务应该使用内网IP去注册。所以你可以在启动的时候直接拿到节点的IP/端口(这个是完全可行的,和是不是传统部署都没有关系)。
- 不知道你是不是使用了自动获取的方式去获取实例的IP,但是注册的时候出了问题,注册的服务都是docker容器内部的172.x开头的地址,这个问题导致了gateway没法正常工作。所以你才想到了去手动指定实例的IP。通过手动指定的方式也可以实现正常注册,但是没有必要多绕一圈。
如果你要用传统的方式部署微服务
- 使用docker启动容器的时候,建议把网络模式改成host,这样在容器内,能直接获取到节点的IP,注册服务、在多节点的环境,gateway在转发时也都能正常工作。启动实例的时候直接
docker run --network host -d image:tag
,就不需要指定额外的参数了。 - docker 默认情况下是只能单个节点运行的,即使 docker-compose 也是一样的。所以在每个节点上,不管怎么样都要考虑到端口分配问题,它需要手动去维护,所以是会有一些麻烦在。
- 还是建议你试试用 docker swarm 来做容器编排,简单方便,虽然现在貌似用的不多,但还是docker官方推荐的生产环境部署方式之一。:https://docs.docker.com/get-started/orchestration/
问题1: 如何动态获取 VERSION
通常情况下,在构建的时候就能拿到版本号,通过脚本读取pom.xml文件(或者通过插件,但是实际上也是读取pom.xml)
问题2: 如何自动获取宿主机的SERVER_NAME
可以通过 “环境变量”
我举一个使用 docker swarm 部署微服务的例子(k8s也类似):
docker-compose.yml ,会启动 gateway / auth / core 三个服务
services: gateway:
image: example/gateway:v1
environment: # 给这个服务添加环境变量
- ENVIRONMENT=prod #当前环境/服务名字/nacos注册信息
- SERVER_NAME=gateway
- NACOS_SERVER=nacos.domain.com
- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx
ports:
- "8080:8080" # 暴露网关的端口
deploy:
mode: replicated
replicas: 2 # 给这个服务部署两个实例,下面的操作都类似。
auth:
image: example/auth:v1
environment:
- ENVIRONMENT=prod
- SERVER_NAME=auth
- NACOS_SERVER=nacos.domain.com
- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx
deploy:
mode: replicated
replicas: 2
core:
image: example/core:v1
environment:
- ENVIRONMENT=prod
- SERVER_NAME=core
- NACOS_SERVER=nacos.domain.com
- NACOS_namespace=e35500e1-2441-4001-b60f-3f7d55bxxxxx
deploy:
mode: replicated
replicas: 2
环境变量,通常都是在“运行时”获取的,但是你可以把一些默认的环境变量,在“构建时”时写入到镜像中。
使用“ARG”也是可以的,但是感觉不太方便。
如果你已经用了微服务,就不建议用 纯docker 去手动管理容器和服务了,你应该把部署的工作交给 “容器编排系统” 来做,比如 k8s / docker-swarm 。
nacos读取配置文件的时候,是可以直接从环境变量中取值的,可以不用手动传参数。
补充一个k8s部署的例子:
apiVersion: apps/v1kind: Deployment
metadata:
name: gateway
spec:
selector:
matchLabels:
app: gateway
template:
metadata:
labels:
app: gateway
spec:
containers:
- name: gateway
image: example/gateway:v1
resources:
limits:
memory: "128Mi"
cpu: "500m"
env:
- name: ENVIRONMENT
value: prod
- name: SERVER_NAME
value: gateway
ports:
- containerPort: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
spec:
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
image: example/auth:v1
resources:
limits:
memory: "128Mi"
cpu: "500m"
env:
- name: ENVIRONMENT
value: prod
- name: SERVER_NAME
value: auth
ports:
- containerPort: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: core
spec:
selector:
matchLabels:
app: core
template:
metadata:
labels:
app: core
spec:
containers:
- name: core
image: example/core:v1
resources:
limits:
memory: "128Mi"
cpu: "500m"
env:
- name: ENVIRONMENT
value: prod
- name: SERVER_NAME
value: core
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: gateway
spec:
selector:
app: gateway
ports:
- port: 8000
targetPort: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
labels:
name: example-ingress
spec:
rules:
- host: example.domain.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: gateway
port:
number: 8000
以上是 SpringCloud微服务部署环境参数动态设置? 的全部内容, 来源链接: utcz.com/p/945508.html